学习笔记:Vue Router基本实现

153 阅读3分钟

一、Vue Router实现原理

Vue Router 基础回顾

Hash模式和History模式

模拟实现自己的Vue Router

1.基本使用

引入路由相关的组件

通过Vue.use注册路由插件

创建路由的对象,配置路由规则

注册路由对象

通过routerview前去占位

routerlink去跳转

动态路由

this.$route.push(/replace/go的方法)

  • Hash和History模式的区别

2.实现原理

2.1前置知识

  • 插件
  • 混入
  • Vue.observable()
  • 插槽
  • render函数
  • 运行时和完整般的Vue
  • Hash模式
    • Url中#后面的内容作为路径地址,当仅改变#号后的内容,浏览器不会向服务器请求,但是会把这个路径存放在历史记录里面
    • #号后的内容发生改变的时候,监听hashchange事件
    • 根据当前路由地址找到对应的组件重新渲染
  • History模式
    • history.pushState()方法改变地址栏目,并把路径存放再历史记录中
    • 监听popstate事件,记录改变地址,当前进后退或则back方法事件才会被触发
    • 监听当前路由地址找到对应的组件重现渲染
  • 类图
    • options:作用是记录构造函数中传入的对象, 我们在创建Vue Router的实例的时候,传递了一个对象,而该对象中定义了路由规则。而options就是记录传入的这个对象的。
    • routeMap:是一个对象,记录路由地址与组件的对应关系,也就是一个键值对的形式,后期会options中路由规则解析到routeMap中。
    • data是一个对象,该对象中有一个属性current,该属性用来记录当前的路由地址,data是一个响应式的对象,因为当前路由地址发生变化后,对应的组件要发生更新(也就说当地址变化后,要加载对应组件)。
    • install是一个静态方法,用来实现Vue的插件机制。
    • Constructor是一个构造方法,该该构造方法中会初始化options ,data,routeMap这几个属性。
    • init方法主要是用来调用下面的三个方法,也就把不同的代码分隔到不同的方法中去实现
      • initEvent方法,用来注册popstate事件,
      • createRouteMap方法,该方法会把构造函数中传入进来的路由规则,转换成键值对的形式存储到routeMap中。 键就是路由的地址,值就是对应的组件
      • initComponents方法,主要作用是用来创建router-link和router-view这两个组件的。

2.2 Vue-router install

  • 知识补充
    • vue.use
      • 安装 Vue.js 插件。如果插件是一个对象,必须提供 install 方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入
    • vue.mixin
      • 混入 (mixins) 是一种分发vue组件中可复用功能的非常灵活的方式。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项。
      • 钩子函数合并同名钩子函数将混合为一个数组,因此都将被调用。另外,混入对象的钩子函数将在组件自身钩子函数之前调用
      • 数据对象合并 数据对象在内部会进行浅合并 (一层属性深度),在和组件的数据发生冲突时以组件数据优先(组件的data中变量会覆盖混入对象的data中变量)
      • 普通方法合并当混合值为对象的选项时,例如 methods、components、directive,将被混合为同一个对象,两个对象键名冲突时,取组件对象的键值对
      • 混入可以全局和局部
    • vue.$options
      • 用于当前 Vue 实例的初始化选项。
let _Vue  = null
export default class VueRouter{
    static install(Vue) {
        // 1.install判断当前插件是否已经被安装
        // 在私有方法上定义个标识,开始的时候没有,然后当存在的是侯就代表安装过
        if(VueRouter.install.installed){
            return
        }
        VueRouter.install.installed = true
        // 2.把vue的构造照函数定义到全局变量中来,后面实例方法需要使用到这个构造函数
        _Vue = Vue 
        // 3.把创建vue实例时传入的router对象,注入到vue的实例上
        // 可以通过混入来给vue实例中添加这个$router方法
        _Vue.mixin({
            beforeCreate (){
                // 判断是否有这个属性,
                if (this.$options.router){
                    _Vue.prototype.$router = this.$optiongs.router
                }
            }
        })
    }

}

2.3 构造函数

该构造方法中会初始化options ,data,routeMap这几个属性。

  • options作用是记录构造函数中传入的对象, 我们在创建Vue Router的实例的时候,传递了一个对象,而该对象中定义了路由规则和一些配置选项。
  • routeMap:是一个对象,记录路由地址与组件的对应关系,也就是一个键值对的形式,实际上是根据我们传入的routes,经行一个数据处理,以后当我们使用的时候就可以根据地址找到对应组件
  • data是一个对象,该对象中有一个属性current,该属性用来记录当前的路由地址,data是一个响应式的对象,因为当前路由地址发生变化后,对应的组件要发生更新(也就说当地址变化后,要加载对应组件)。

知识补充

  • Vue.observable( object )
    • 让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象。返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新。
constructor(options){
    this.options = options;//记录构造函数中传入的选项
    this,routeMap ={};
    this.data = _Vue.observable({
        current: "/",
    }); 
}

2.4 createRouteMap()

createRouteMap方法,该方法会把构造函数中传入进来的options参数中的路由规则(routes),转换成键值对的形式存储到routeMap中。 键就是路由的地址,值就是对应的组件

createRouteMap(){
    // 遍历所有地址,把路由规则接新城键值对的形式,存储到routeMap中
    this.options.routes.map( route => {
        this.routeMap[route.path] = route.component
    })
}

2.5 initComponents()

  • initComponents方法,主要作用是用来创建router-linkrouter-view这两个组件的。

  • createRouteMap()initComponents()在实例注册的时候挂载

  • 需要手写自己的render函数生成对应的a 标签

  • a标签需要跳转到对应路由(注意式不刷新页面,并且改变地址)

  • router-view通过响应式的地址指向对应的vue组件

initComponents(Vue) {
    //传入有Vue的构造函数,减少类对外部的依赖
    //创建router——link组件,router——link最终会被渲染成一个标签
    Vue.component('router-link', {
        props: {
            to: String
        },
        // template: '<a :href = "to"><slot></slot></a>'
        render(h){
            return h('a',{ 
                attrs: {
                    href: this.to
                },
                on:{
                    click:this.handleClick
                }
            },[this.$slots.default])
        },
        methods:{
            handleClick(e){
                // 改变地址栏
                history.pushState({},'',this.to)
                // 注意这里的this,指向的这个组件,通过找到这个构造数的原型上的响应式的data.current
                this.$router.data.current = this.to 
                 // 这里引入事件对象,并且组织a标签的默认行为 
                e.preventDefault()
            }
        }
        
    })
    // 实现router-view组件
    // self指向一个vue-router实例
    const self = this
    Vue.component('router-view', {
        // 创建并返回一个虚拟dom
        render(h){
            // 通过地址找到对应的组件
            const component = self.routeMap[self.data.current]
            return h(component)
        }
    })
}

2.6 initEvent()

  • 该事件用来解决浏览器的前进及后退
  • popstate
    • 当活动历史记录条目更改时,将触发popstate事件。如果被激活的历史记录条目是通过对history.pushState()的调用创建的,或者受到对history.replaceState()的调用的影响,popstate事件的state属性包含历史条目的状态对象的副本。
// initEvent 事件用来处理popstates,注意在init方法中调用
initEvent() {
    window.addEventListener('popstate' , () => {
        //注意的式这里this直线vue的实例
        this.data.current = window.location.pathname
    })
}

3.完整代码

let _Vue = null
export default class VueRouter {
    static install(Vue) {
        if (VueRouter.install.installed) {
            return
        }
        VueRouter.install.installed = true
        _Vue = Vue
        _Vue.mixin({
            beforeCreate() {
                if (this.$options.router) {
                    _Vue.prototype.$router = this.$options.router
                    this.$options.router.init()
                }
            }
        })
    }

    constructor(options) {
        this.options = options;
        this.routeMap = {};
        this.data = _Vue.observable({
            current: "/",
        });
    }
    init() { 
        this.createRouteMap()
        this.initComponents(_Vue)
        this.initEvent()
    }

    createRouteMap() {
        this.options.routes.map(route => {
            this.routeMap[route.path] = route.component
        })
    }

    initComponents(Vue) {
        Vue.component('router-link', {
            props: {
                to: String
            },
            render(h){
                return h('a',{ 
                    attrs: {
                        href: this.to
                    },
                    on:{
                        click:this.handleClick
                    }
                },[this.$slots.default])
            },
            methods:{
                handleClick(e){
                    history.pushState({},'',this.to)
                    this.$router.data.current = this.to 
                    e.preventDefault()

                }
            }
            
        })
        const self = this
        Vue.component('router-view', {
            render(h){
                const component = self.routeMap[self.data.current]
                return h(component)
            }
        })
    }
    initEvent() {
        window.addEventListener('popstate' , () => {
            this.data.current = window.location.pathname
        })
    }
}