JavaScript封裝Vue-Router實現(xiàn)流程詳解
摘要
前段時間對Vue-router里面的一些方法原理進行了實現(xiàn),在這里進行整理一下,相比于上一篇實現(xiàn)的axios,這次實現(xiàn)的路由顯得更復雜一些。
實現(xiàn)的方法主要有push,go,replace以及beforeEach和afterEach的鉤子函數(shù)。
實現(xiàn)的原理主要有URL和頁面的響應,以及router-view和router-link。
1.hash和history
在寫之前我們要了解hash和history的區(qū)別,主要的區(qū)別還是在于
hash模式更改路由不需要重新請求,并且URL中路由前面有#。
history模式下,URL中路由前面沒有#,并且更改URL會重新請求。
我們不管對于什么方法的實現(xiàn)都要兼容這兩種模式。
而Vue-router在創(chuàng)建實例的時候也可以進行設置是hash模式還是history模式。
所以我們的類在創(chuàng)建的時候需要這么寫:
function myrouter (obj) { this.mode = obj.mode || 'hash' }
2.push go replace方法
首先我們還沒有實現(xiàn)router-view,所以在頁面中我們先使用一個h1標簽來代替。
(1) push
對于hash模式,我們直接更改URL的hash值就可以。
但在history模式下,我們需要使用pushState這個方法。
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是判斷是否為對象的方法:
//判斷參數(shù)類型 function ifObj (data) { if (typeof data == 'object') { return data.path } else { return data; } }
(2) go
對于go方法,就顯得更簡單了,直接用就可:
myrouter.prototype.go = function (num) { history.go(num) }
(3) replace
對于replace方法,我們在
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,對于這三個方法我們已經(jīng)實現(xiàn)了最基本的功能,但是在后面還需要對其進行更改。包括路由攔截都會影響這三個方法。
3.監(jiān)聽方法
首先即便是我們實現(xiàn)了這三個方法,但是,如果我們直接在URL中更改路由,頁面是依舊不會給響應的,所以我們要對頁面的URL進行監(jiān)聽。
而window自帶了一些監(jiān)聽URL的方法:
對于hash模式:onhashchange可以監(jiān)聽hash值的變化。
對于history模式:onpopstatea可以監(jiā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模式下前進后退的操作 window.addEventListener('popstate', function () { h1.innerHTML = location.pathname }); window.onload = function () { h1.innerHTML = location.pathname } } }
這里面,onload的作用主要是,在history模式下,我們更改頁面的URL會刷新頁面,所以我們采用onload方法來進行監(jiān)聽。
4.beforeEach鉤子函數(shù)
我們知道,beforeEach鉤子函數(shù)接受了一個回調(diào)函數(shù)作為參數(shù),所以在我們的源碼里面我們需要將這個回調(diào)函數(shù)保存下來:
myrouter.prototype.beforeEach = function (config) { this.saveBefore = config; }
而這個回調(diào)函數(shù)也接受三個參數(shù),所以我們的構(gòu)造函數(shù)要進行更改:
function myrouter (obj) { this.mode = obj.mode || 'hash' this.saveBefore = null this.saveAfter = null this.from = '' this.to = '' }
而我們不管在什么方法之中,都要時刻更新to和from的值(push,replace,go,監(jiān)聽方法)
而這個鉤子函數(shù)的回調(diào)函數(shù)的第三個參數(shù)是傳了一個next的回調(diào)函數(shù)(有點繞)
但是重點我們應該放在這個next的回調(diào)函數(shù)上,我們知道next方法可以接受參數(shù)的方式有三種:
true / false : 繼續(xù)執(zhí)行/ 停止執(zhí)行
string : 跳轉(zhuǎn)到某個路由
空 : 停止執(zhí)行(或者不寫next)
所以next方法中
我們要對參數(shù)進行類型判斷,而對于每種類型我們還要對hash和history模式進行判斷:
myrouter.prototype.next = function () { //當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 } //當next中參數(shù)是某個路徑 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] } //當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)用這個方法進行路由攔截。
5.router-link
其實實現(xiàn)出router-link的話,應該比較簡單,我們直接自己封裝出一個子組件,在main.js中引入其實就可。
其中主要有的就是父組件向子組件傳值的問題:
在這里我就直接把代碼拿過來了:
<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的實現(xiàn)需要考慮的可能就多一些,我們可以再實現(xiàn)一個子組件,然后把之前的h1標簽替換成現(xiàn)在的子組件。
<template> <div class="routerview"></div> </template>
但是這種情況我們不能實現(xiàn)嵌套路由。
所以我們的解決辦法是:
對path和routers進行對應匹配,然后進行匹配的值的層記錄下來,再對對應層的router-view進行渲染。
。。。。md說不明白。
emmm,這就要靠自己理解了。。。。
對于以上所以的方法和原理,因為要粘貼代碼可能伴隨著刪減,可能出現(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 () { //當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 } //當next中參數(shù)是某個路徑 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] } //當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模式下前進后退的操作 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實例的方法 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 myrouter
8.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頁面</h1>' } const b = { template: '<h1>b頁面</h1>' } const child1 = { template: '<h1>child1頁面</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實現(xiàn)流程詳解的文章就介紹到這了,更多相關(guān)JS封裝Vue-Router內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用JavaScript和MQTT開發(fā)物聯(lián)網(wǎng)應用示例解析
這篇文章主要介紹了使用JavaScript和MQTT開發(fā)物聯(lián)網(wǎng)應用示例解析,文章通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-08-08ZeroClipboard插件實現(xiàn)多瀏覽器復制功能(支持firefox、chrome、ie6)
Zero Clipboard 利用透明的Flash讓其漂浮在復制按鈕之上,這樣其實點擊的不是按鈕而是Flash ,這樣將需要的內(nèi)容傳入Flash,再通過Flash的復制功能把傳入的內(nèi)容復制到剪貼板2014-08-08