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ù),所以我們的構造函數(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 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頁面</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,
})到此這篇關于JavaScript封裝Vue-Router實現(xiàn)流程詳解的文章就介紹到這了,更多相關JS封裝Vue-Router內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
JS關于for循環(huán)中使用setTimeout的四種解決方案
這篇文章主要介紹了JS關于for循環(huán)中使用setTimeout的四種解決方案,想深入了解JS的同學,一定要看下2021-05-05
Three.js利用性能插件stats實現(xiàn)性能監(jiān)聽的方法
Three.js 是一款運行在瀏覽器中的 3D 引擎,你可以用它創(chuàng)建各種三維場景,而下面這篇文章主要給大家介紹了關于Three.js如何利用性能插件stats實現(xiàn)性能監(jiān)聽的相關資料,需要的朋友可以參考借鑒,下面來一起學習學習吧。2017-09-09

