【细嚼慢咽】从零实现手写Vue-Router

2,686 阅读2分钟

很多时候面试如果你的技术栈中有Vue,那么会经常被问到Vue-Router的相关知识,所以今天给大家带来Vue-Router路由的全面分析,助你过五关斩六将,工资蹭蹭涨。

如果对您有所帮助,可以点个小赞咯~ ~

1.Vue-Router原理

1.总的说来,Vue-Router通过监听浏览器History对象的变化,再根据路由表中配置的路由对应关系,通过RouterView组件渲染到VuePage上,从而实现不刷新页面并跳转。

那么问题来了,首先,如何监听History对象变化?

监听History变化

通常来说,在Window对象上有两个事件可以实现对History对象变化的监听:

1. onhashchange

可以用来监听URL中“#”后的hash变化。



例如URL本来为:https://juejin.im/editor/drafts/6890734538818125837#ab
此时改变#后的hash值为#abc,页面不会触发请求或更新,但会触发onhashchange的监听事件,
通过监听hash的改变来实现单页面路由的切换。
window.onhashchange = function(){
    console.log(location.hash) //abc
}

2. popstate

可以用来监听History栈的改变

popstate可以用来监听浏览器点击前进/后退按钮、或者是调用了history.back()、history.go()、history.forward()、时均会触发API;
window.addEventListener('popstate',()=>{
	console.log('1',history)
})

//tips:Html5中可以用
//history.pushState({},"title","anotherpage.html")
//或history.replaceState({},"title","anotherpage.html")
//来添加或重置当前History栈中最后一项,但这两个API调用时并不会触发popstate的监听。

	

2.从零开始的Vue-router手写

本文这次手写主要使用的是hash模式,主要分为3个步骤:

1.实现vue-router的插件功能,使引入手写的vue-router后能够直接使用vue.use()方法把router实例挂载到vue上,从而能够全局访问Vue.$router

项目初始化后,先在src目录新建self_router目录,里面包含index.js和self_router.js

1.1 其中index.js内容与平常使用router中的index.js一样,主要作用是将 router配置对象传入 new self_router()中并将self_router实例暴露出去。

index.js如下:

import Vue from 'vue'
import VueRouter from './self_router.js' //自定义router
import Home from '../views/Home.vue'

Vue.use(VueRouter)
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: function () {
      return import(/* webpackChunkName: "about" */ '../views/About.vue')
    }
  }
]

const router = new VueRouter({
  mode: 'hash',
  base: process.env.BASE_URL,
  routes
})

export default router

初始化self_router.js 主要是实现在index.js中能够引入自身并use,然后可以new 一个self_router的实例。

vue.use(xxx)方法实际上时调用了xxx.install()方法,所以我们在self_router的install方法中完成vue中对router实例的挂载。

self_router.js代码如下:

    let self_vue;

class self_router {
    constructor(options){ //这里的options是index.js中new router时传入的配置对象
        this.options = options;
    }
}
self_router.install = function(Vue){
    self_vue = Vue;
    Vue.mixin({
        beforeCreate(){
            if (this.$options.router){
                Vue.prototype.$router = this.$options.router;
            }
        }
    })

   
}
module.exports = self_router

因为要确保vue.$router先传入router实例,再挂载到vue上,所以在install中使用vue.mixin()方法,在beforeCreate生命周期中再执行挂载,确保此时已传入了router实例。 运行项目,会发现此时控制台报错:“router-link”和“router-view”组件不存在,所以开始第二步。

2.实现router-view 和 router-link 两个自定义组件的全局注册和基本功能,使用户在点击router-link时,能够触发url中hash的改变。

self_router.js中self_router.install方法改写入下:

self_router.install = function(Vue){
    self_vue = Vue;
    Vue.mixin({
        beforeCreate(){
            if (this.$options.router){
                Vue.prototype.$router = this.$options.router;
            }
        }
    })

    Vue.component('router-link',{//注册router-link组件
        props:{
            to:{//router-link组件调用时的父组件传参 “to”
                type:String,
                required:true
            }
        },
        render(h){//用渲染函数写了一个router-link,
                  //相当于写了一个template组件或是用jsx写了个组件
            return h('a',{attrs:{href:"#"+this.to}},this.$slots.default) //参数分别为:tagName,attr,是vue默认的子节点内容
        }
    })
    Vue.component('router-view',{//注册router-view组件,这一步只是先保证不报错
        render(h){
            return h('div','view')
        }
    })
}

此时,点击页面中的不同router-link,已经能实现url中的对应hash变换。

3.对url的hash值进行监听,当其改变时,触发渲染对应的route中的组件。此时,点击router-link或修改hash值都可以触发router-view中视图层的更新。

self_router.js完整版如下

let self_vue;

class self_router {
    constructor(options) { //这里的options是index.js中new router时传入的配置对象
        this.options = options;
        this.initHash = window.location.hash.slice(1) || '/';
        //创建一个私有属性,表示最新的hash值,URL中hash值变化时改变这个值,这个值变化时,通知视图更新(通过将这个私有属性变为响应式即可)。
        self_vue.util.defineReactive(this,'current',this.initHash)
        //监听hash发生变化
        window.addEventListener('hashchange', this.onHashChange.bind(this))//调用bind是因为让onhashchange的this指向self_router
        window.addEventListener('onload', this.onHashChange.bind(this))
    }
    //hash变化时,改变this.current
    onHashChange() {
        this.current = window.location.hash.slice(1)//截取#之后的hash字符串
    }
}
self_router.install = function (Vue) {
    self_vue = Vue;
    Vue.mixin({
        beforeCreate() {
            if (this.$options.router) {
                Vue.prototype.$router = this.$options.router;
            }
        }
    })

    Vue.component('router-link', {//注册router-link组件
        props: {
            to: {//router-link组件调用时的父组件传参 “to”
                type: String,
                required: true
            }
        },
        render(h) {//渲染函数写了一个建议的router-link,
            //相当于写了一个template组件或是用jsx写了个组件
            return h('a', { attrs: { href: "#" + this.to } }, this.$slots.default) //参数分别为:tagName,attr,是vue默认的子节点内容
        }
    })
    Vue.component('router-view', {//注册router-view组件,这一步只是先保证不报错
        render(h) {
            const routes = this.$router.options.routes;
            let currentRoute = routes.find((item) => {
                return item.path == this.$router.current
            })

            let renderComponent = currentRoute.component || null
            return h(renderComponent)
        }
    })
}
module.exports = self_router

此时,点击router-link或者修改url中的hash值,router-view中会自动根据传入的routes来渲染对应的组件视图。 综上3步,已完成vue-Router基础版的自定义手写,如果对你有所帮助,那将是我莫大的荣幸!