JavaScript封裝Vue-Router實(shí)現(xiàn)流程詳解
摘要
前段時(shí)間對(duì)Vue-router里面的一些方法原理進(jìn)行了實(shí)現(xiàn),在這里進(jìn)行整理一下,相比于上一篇實(shí)現(xiàn)的axios,這次實(shí)現(xiàn)的路由顯得更復(fù)雜一些。
實(shí)現(xiàn)的方法主要有push,go,replace以及beforeEach和afterEach的鉤子函數(shù)。
實(shí)現(xiàn)的原理主要有URL和頁(yè)面的響應(yīng),以及router-view和router-link。
1.hash和history
在寫之前我們要了解hash和history的區(qū)別,主要的區(qū)別還是在于
hash模式更改路由不需要重新請(qǐng)求,并且URL中路由前面有#。
history模式下,URL中路由前面沒有#,并且更改URL會(huì)重新請(qǐng)求。
我們不管對(duì)于什么方法的實(shí)現(xiàn)都要兼容這兩種模式。
而Vue-router在創(chuàng)建實(shí)例的時(shí)候也可以進(jìn)行設(shè)置是hash模式還是history模式。
所以我們的類在創(chuàng)建的時(shí)候需要這么寫:
function myrouter (obj) {
this.mode = obj.mode || 'hash'
}
2.push go replace方法
首先我們還沒有實(shí)現(xiàn)router-view,所以在頁(yè)面中我們先使用一個(gè)h1標(biāo)簽來代替。
(1) push
對(duì)于hash模式,我們直接更改URL的hash值就可以。
但在history模式下,我們需要使用pushState這個(gè)方法。
myrouter.prototype.push = function (data) {
//判斷路由模式
if (this.mode == 'hash') {
location.href = orgin + '/#' + ifObj(data);
h1.innerHTML = ifObj(data)
}
else if (this.mode = 'history') {
history.pushState({}, '', orgin + ifObj(data))
h1.innerHTML = ifObj(data)
}
}
ifObj是判斷是否為對(duì)象的方法:
//判斷參數(shù)類型
function ifObj (data) {
if (typeof data == 'object') {
return data.path
} else {
return data;
}
}
(2) go
對(duì)于go方法,就顯得更簡(jiǎn)單了,直接用就可:
myrouter.prototype.go = function (num) {
history.go(num)
}
(3) replace
對(duì)于replace方法,我們?cè)?/p>
hash模式下,需要調(diào)用location.replace()方法。
history模式下,需要調(diào)用replaceState()方法。
myrouter.prototype.replace = function (data) {
let orgin = location.origin;
//判斷路由模式
if (this.mode == 'hash') {
//判斷是否使用路由攔截
location.replace(orgin + '/#' + ifObj(data));
h1.innerHTML = ifObj(data)
} else if (this.mode == 'history') {
history.replaceState({}, '', orgin + ifObj(data))
h1.innerHTML = ifObj(data)
}
}OK,對(duì)于這三個(gè)方法我們已經(jīng)實(shí)現(xiàn)了最基本的功能,但是在后面還需要對(duì)其進(jìn)行更改。包括路由攔截都會(huì)影響這三個(gè)方法。
3.監(jiān)聽方法
首先即便是我們實(shí)現(xiàn)了這三個(gè)方法,但是,如果我們直接在URL中更改路由,頁(yè)面是依舊不會(huì)給響應(yīng)的,所以我們要對(duì)頁(yè)面的URL進(jìn)行監(jiān)聽。
而window自帶了一些監(jiān)聽URL的方法:
對(duì)于hash模式:onhashchange可以監(jiān)聽hash值的變化。
對(duì)于history模式:onpopstatea可以監(jiān)聽前進(jìn)后退等的操作。
myrouter.prototype.update = function () {
let _this = this;
//判斷路由模式
if (this.mode == 'hash') {
//監(jiān)聽url哈希值的變化
window.onhashchange = function () {
h1.innerHTML = location.hash.replace('#/', '')
}
}
else if (this.mode == 'history') {
//監(jiān)聽history模式下前進(jìn)后退的操作
window.addEventListener('popstate', function () {
h1.innerHTML = location.pathname
});
window.onload = function () {
h1.innerHTML = location.pathname
}
}
}
這里面,onload的作用主要是,在history模式下,我們更改頁(yè)面的URL會(huì)刷新頁(yè)面,所以我們采用onload方法來進(jìn)行監(jiān)聽。
4.beforeEach鉤子函數(shù)
我們知道,beforeEach鉤子函數(shù)接受了一個(gè)回調(diào)函數(shù)作為參數(shù),所以在我們的源碼里面我們需要將這個(gè)回調(diào)函數(shù)保存下來:
myrouter.prototype.beforeEach = function (config) {
this.saveBefore = config;
}
而這個(gè)回調(diào)函數(shù)也接受三個(gè)參數(shù),所以我們的構(gòu)造函數(shù)要進(jìn)行更改:
function myrouter (obj) {
this.mode = obj.mode || 'hash'
this.saveBefore = null
this.saveAfter = null
this.from = ''
this.to = ''
}
而我們不管在什么方法之中,都要時(shí)刻更新to和from的值(push,replace,go,監(jiān)聽方法)
而這個(gè)鉤子函數(shù)的回調(diào)函數(shù)的第三個(gè)參數(shù)是傳了一個(gè)next的回調(diào)函數(shù)(有點(diǎn)繞)
但是重點(diǎn)我們應(yīng)該放在這個(gè)next的回調(diào)函數(shù)上,我們知道next方法可以接受參數(shù)的方式有三種:
true / false : 繼續(xù)執(zhí)行/ 停止執(zhí)行
string : 跳轉(zhuǎn)到某個(gè)路由
空 : 停止執(zhí)行(或者不寫next)
所以next方法中
我們要對(duì)參數(shù)進(jìn)行類型判斷,而對(duì)于每種類型我們還要對(duì)hash和history模式進(jìn)行判斷:
myrouter.prototype.next = function () {
//當(dāng)next中沒有參數(shù)
if (arguments[0] == undefined) {
//判斷路由模式
if (this.mode == 'hash') {
let str = location.origin + '/#' + this.to
//判斷是什么方法
if (this.methodType == 'replace') {
location.replace(str)
} else if (this.methodType == 'push') {
location.href = str
}
h1.innerHTML = str;
} else if (this.mode = 'history') {
let str = location.origin + this.to
if (this.methodType == 'push') {
history.pushState({}, '', str)
} else if (this.methodType == 'replace') {
history.replaceState({}, '', str)
}
h1.innerHTML = str;
}
this.from = this.to
}
//當(dāng)next中參數(shù)是某個(gè)路徑
else if (typeof arguments[0] == 'string') {
//判斷路由模式
if (this.mode == 'hash') {
location.hash = arguments[0]
} else if (this.mode = 'history') {
let str = location.origin + arguments[0]
//判斷是什么方法
if (this.methodType == 'push') {
history.pushState({}, '', str)
} else if (this.methodType == 'replace') {
history.replaceState({}, '', str)
}
h1.innerHTML = str;
}
this.to = arguments[0]
}
//當(dāng)next里面?zhèn)魅肫渌麉?shù)(false)或者沒有使用next方法
else {
if (this.mode == 'hash') {
location.hash = this.from
} else if (this.mode = 'history') {
if (this.from) {
let str = location.origin + this.from
if (this.methodType == 'push') {
history.pushState({}, '', str)
} else if (this.methodType == 'replace') {
history.replaceState({}, '', str)
}
} else {
history.back()
}
}
this.to = this.from
}
}OK,next方法寫好了之后,我們只需要在push,replace,go,監(jiān)聽的方法之中,判斷this.saveBefore是否為空,如果不為空,我們就調(diào)用這個(gè)方法進(jìn)行路由攔截。
5.router-link
其實(shí)實(shí)現(xiàn)出router-link的話,應(yīng)該比較簡(jiǎn)單,我們直接自己封裝出一個(gè)子組件,在main.js中引入其實(shí)就可。
其中主要有的就是父組件向子組件傳值的問題:
在這里我就直接把代碼拿過來了:
<template>
<a @click="fn" class="routerlink">
<slot></slot>
</a>
</template>
<script>
import router from '../myrouter'
export default {
name : 'routerLink',
props : ['to','replace'],
data(){
return {
title : 'router-link'
}
},
created(){
},
methods : {
fn(){
if(this.to){
this.$myrouter.push(this.to);
}else if(this.replace){
this.$myrouter.replace(this.replace)
}
}
}
}
</script>
<style scoped>
.routerlink{
cursor:pointer
}
</style>6.router-view
router-view的實(shí)現(xiàn)需要考慮的可能就多一些,我們可以再實(shí)現(xiàn)一個(gè)子組件,然后把之前的h1標(biāo)簽替換成現(xiàn)在的子組件。
<template> <div class="routerview"></div> </template>
但是這種情況我們不能實(shí)現(xiàn)嵌套路由。
所以我們的解決辦法是:
對(duì)path和routers進(jìn)行對(duì)應(yīng)匹配,然后進(jìn)行匹配的值的層記錄下來,再對(duì)對(duì)應(yīng)層的router-view進(jìn)行渲染。
。。。。md說不明白。
emmm,這就要靠自己理解了。。。。
對(duì)于以上所以的方法和原理,因?yàn)橐迟N代碼可能伴隨著刪減,可能出現(xiàn)問題,所以最后我把自己寫的源碼直接粘貼過來吧。
7.myrouter.js代碼
import Vue from 'vue'
var routerview = document.getElementsByClassName('routerview')
function myrouter (obj) {
this.mode = obj.mode || 'hash'
this.saveBefore = null
this.saveAfter = null
this.from = ''
this.to = ''
this.methodType = ''
this.routes = obj.routes;
}
myrouter.prototype.push = function (data) {
this.methodType = 'push'
let orgin = location.origin;
//判斷路由模式
if (this.mode == 'hash') {
this.to = ifObj(data)
this.from = location.hash.replace('#', '');
//判斷是否使用路由攔截
if (this.saveAfter) {
this.saveAfter(this.to, this.from);
}
if (this.saveBefore) {
this.saveBefore(this.to, this.from, this.next)
} else {
location.href = orgin + '/#' + ifObj(data);
h1.innerHTML = returnView(ifObj(data), this.routes)
}
}
else if (this.mode = 'history') {
this.to = ifObj(data)
this.from = location.pathname;
if (this.saveAfter) {
this.saveAfter(this.to, this.from);
}
if (this.saveBefore) {
this.saveBefore(this.to, this.from, this.next)
} else {
history.pushState({}, '', orgin + ifObj(data))
routerview.innerHTML = ''
routerview.appendChild(returnView(ifObj(data).replace('/', ''), this.routes))
}
}
}
myrouter.prototype.go = function (num) {
this.methodType = 'go'
history.go(num)
}
myrouter.prototype.replace = function (data) {
this.methodType = 'replace'
let orgin = location.origin;
//判斷路由模式
if (this.mode == 'hash') {
//判斷是否使用路由攔截
if (this.saveAfter) {
this.saveAfter(this.to, this.from);
}
if (this.saveBefore) {
this.to = ifObj(data)
this.from = location.hash.replace('#', '')
this.saveBefore(this.to, this.from, this.next)
} else {
location.replace(orgin + '/#' + ifObj(data));
routerview.innerHTML = ''
routerview.appendChild(ifObj(data).replace('/', ''))
}
} else if (this.mode == 'history') {
if (this.saveAfter) {
this.saveAfter(this.to, this.from);
}
if (this.saveBefore) {
this.to = ifObj(data)
this.from = location.pathname;
this.saveBefore(this.to, this.from, this.next)
} else {
history.replaceState({}, '', orgin + ifObj(data))
routerview.innerHTML = ''
routerview.appendChild(ifObj(data).replace('/', ''))
}
}
}
//鉤子的next回調(diào)函數(shù)
myrouter.prototype.next = function () {
//當(dāng)next中沒有參數(shù)
if (arguments[0] == undefined) {
//判斷路由模式
if (this.mode == 'hash') {
let str = location.origin + '/#' + this.to
//判斷是什么方法
if (this.methodType == 'replace') {
location.replace(str)
} else if (this.methodType == 'push') {
location.href = str
}
let arr = this.to.split('/');
let path = '/' + arr[arr.length - 1]
let com = (ifChild(this.to, this.routes))
// let path = ('/' + location.hash.replace('#', '').split('/').pop());
// let com = (ifChild(location.hash.replace('#', ''), this.routes))
routerview[routerview.length - 1].innerHTML = ''
routerview[routerview.length - 1].appendChild(returnView(path, com))
} else if (this.mode = 'history') {
let str = location.origin + this.to
if (this.methodType == 'push') {
history.pushState({}, '', str)
} else if (this.methodType == 'replace') {
history.replaceState({}, '', str)
}
routerview.innerHTML = ''
routerview.appendChild(returnView(location.pathname, this.routes))
}
this.from = this.to
}
//當(dāng)next中參數(shù)是某個(gè)路徑
else if (typeof arguments[0] == 'string') {
//判斷路由模式
if (this.mode == 'hash') {
location.hash = arguments[0]
} else if (this.mode = 'history') {
let str = location.origin + arguments[0]
//判斷是什么方法
if (this.methodType == 'push') {
history.pushState({}, '', str)
} else if (this.methodType == 'replace') {
history.replaceState({}, '', str)
}
routerview.innerHTML = ''
routerview.appendChild(returnView(location.pathname, this.routes))
}
this.to = arguments[0]
}
//當(dāng)next里面?zhèn)魅肫渌麉?shù)(false)或者沒有使用next方法
else {
if (this.mode == 'hash') {
location.hash = this.from
} else if (this.mode = 'history') {
if (this.from) {
let str = location.origin + this.from
if (this.methodType == 'push') {
history.pushState({}, '', str)
} else if (this.methodType == 'replace') {
history.replaceState({}, '', str)
}
} else {
history.back()
}
}
this.to = this.from
}
}
//前置鉤子函數(shù)(主要用于保存回調(diào)函數(shù))
myrouter.prototype.beforeEach = function (config) {
this.saveBefore = config;
}
//后置鉤子函數(shù)
myrouter.prototype.afterEach = function (config) {
this.saveAfter = config
}
//掛在在window上的監(jiān)聽方法
myrouter.prototype.update = function () {
let _this = this;
//判斷路由模式
if (this.mode == 'hash') {
//監(jiān)聽url哈希值的變化
window.onhashchange = function () {
//判斷是否使用路由攔截
if (_this.saveAfter) {
_this.saveAfter(_this.to, _this.from);
}
let length = location.hash.replace('#', '').split('/').length - 1;
if (routerview[length]) {
routerview[length].remove()
}
if (_this.saveBefore) {
_this.to = location.hash.replace('#', '')
_this.saveBefore(_this.to, _this.from, _this.next)
} else {
routerview.innerHTML = ''
routerview.appendChild(returnView(location.hash.replace('#/', ''), this.routes))
}
}
window.onload = function () {
if (location.hash.length == 0) {
location.href = location.origin + '/#' + location.pathname
}
if (_this.saveAfter) {
_this.saveAfter(_this.to, _this.from)
}
let arr = location.hash.replace('#', '').split('/');
arr.shift()
let too = ''
arr.forEach(val => {
if (_this.saveBefore) {
too += ('/' + val)
_this.to = too
_this.saveBefore(_this.to, _this.from, _this.next)
} else {
routerview.innerHTML = ''
routerview.appendChild(returnView(location.hash.replace('#/', ''), this.routes))
}
})
}
}
else if (this.mode == 'history') {
//監(jiān)聽history模式下前進(jìn)后退的操作
window.addEventListener('popstate', function () {
_this.methodType = 'go'
//判斷是否使用路由攔截
if (_this.saveAfter) {
_this.saveAfter(_this.to, _this.from);
}
if (_this.saveBefore) {
_this.to = location.pathname
_this.saveBefore(_this.to, _this.from, _this.next)
} else {
routerview.innerHTML = ''
routerview.appendChild(returnView(location.pathname, this, routes))
}
});
window.onload = function () {
if (location.hash.length != 0) {
location.href = location.href.replace('/#', '')
}
if (_this.saveAfter) {
_this.saveAfter(_this.to, _this.from);
}
if (_this.saveBefore) {
_this.to = location.pathname
_this.saveBefore(_this.to, _this.from, _this.next)
} else {
routerview.innerHTML = ''
routerview.appendChild(returnView(location.pathname, this.routes))
}
}
}
}
//判斷參數(shù)類型
function ifObj (data) {
if (typeof data == 'object') {
return data.path
} else {
return data;
}
}
//通過路徑path返回dom實(shí)例的方法
function returnView (path, routes) {
// debugger
if (path && routes) {
for (var i = 0; i < routes.length; i++) {
if (routes[i].path == path) {
if (typeof routes[i].component.template == 'string') {
let div = document.createElement('div');
div.innerHTML = routes[i].component.template
return div
} else {
return toDom(routes[i].component)
}
}
}
}
var div = document.createElement('div');
div.innerHTML = '<h1>404</h1>'
return div
}
function ifChild (path, routes) {
let arr = path.replace('/', '').split('/');
return returnChild(arr, routes);
}
function returnChild (arr, routes) {
if (arr && routes) {
if (arr.length == 1) {
for (var i = 0; i < routes.length; i++) {
if (arr[0] == routes[i].path.replace('/', '')) {
return routes
}
}
} else {
for (var i = 0; i < routes.length; i++) {
if (arr[0] == routes[i].path.replace('/', '')) {
arr.shift();
return returnChild(arr, routes[i].children)
}
}
}
}
}
//將vue組建轉(zhuǎn)換成dom的方法
function toDom (view) {
const Instance = new Vue({
render (h) {
return h(view);
}
});
const component = Instance.$mount();
return component.$el
}
export default myrouter8.main.js代碼
import Vue from 'vue'
import App from './App.vue'
// import router from './router'
import myrouter from '../router/myrouter'
import view1 from '../router/view/view1.vue'
import view2 from '../router/view/view2.vue'
import child2 from '../router/view/child2.vue'
const a = { template: '<h1>a頁(yè)面</h1>' }
const b = { template: '<h1>b頁(yè)面</h1>' }
const child1 = { template: '<h1>child1頁(yè)面</h1>' }
const routes = [
{
path: '/a', component: view2, children: [
{ path: '/achild1', component: child1 },
{ path: '/achild2', component: child2 }
]
},
{ path: '/b', component: b },
{ path: '/c', component: view1 }
]
let router = new myrouter({
mode: 'hash',
routes: routes
})
router.beforeEach((to, from, next) => {
// console.log(to, from);
if (to == '/a') {
router.next()
} else if (to == '/b') {
router.next()
} else if (to == '/c') {
router.next()
} else if (to == '/d') {
router.next()
} else {
router.next()
}
})
router.afterEach((to, from) => {
if (to == '/a' && from == '/b') {
console.log('from b to a');
}
})
Vue.prototype.$myrouter = router;
new Vue({
el: '#app',
render: h => h(App),
// router,
})到此這篇關(guān)于JavaScript封裝Vue-Router實(shí)現(xiàn)流程詳解的文章就介紹到這了,更多相關(guān)JS封裝Vue-Router內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
layui.js實(shí)現(xiàn)的表單驗(yàn)證功能示例
這篇文章主要介紹了layui.js實(shí)現(xiàn)的表單驗(yàn)證功能,結(jié)合實(shí)例形式分析了基于layui.js的事件監(jiān)聽、驗(yàn)證、判定等相關(guān)操作技巧,需要的朋友可以參考下2017-11-11
JS關(guān)于for循環(huán)中使用setTimeout的四種解決方案
這篇文章主要介紹了JS關(guān)于for循環(huán)中使用setTimeout的四種解決方案,想深入了解JS的同學(xué),一定要看下2021-05-05
原生JavaScript實(shí)現(xiàn)彈幕組件的示例代碼
這篇文章主要介紹了原生JavaScript實(shí)現(xiàn)彈幕組件的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
js動(dòng)態(tài)修改整個(gè)頁(yè)面樣式達(dá)到換膚效果
這篇文章主要介紹了通過js動(dòng)態(tài)修改整個(gè)頁(yè)面樣式達(dá)到換膚效果,需要的朋友可以參考下2014-05-05
Three.js利用性能插件stats實(shí)現(xiàn)性能監(jiān)聽的方法
Three.js 是一款運(yùn)行在瀏覽器中的 3D 引擎,你可以用它創(chuàng)建各種三維場(chǎng)景,而下面這篇文章主要給大家介紹了關(guān)于Three.js如何利用性能插件stats實(shí)現(xiàn)性能監(jiān)聽的相關(guān)資料,需要的朋友可以參考借鑒,下面來一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09
JS實(shí)現(xiàn)進(jìn)度條順滑版詳細(xì)方案
最近在小程序里,做了一個(gè)類似微博刷視頻的需求,其中有一部分功能需要實(shí)現(xiàn)自定義進(jìn)度條,在做完第一版之后發(fā)現(xiàn)進(jìn)度條不順滑,而后想查查網(wǎng)上看有沒有什么好的方案,但最終沒找到合適的。下面給大家分享JS進(jìn)度條順滑版實(shí)現(xiàn)代碼,需要的朋友參考下吧2021-08-08

