【学习笔记】手写Vue-Router(一)

112 阅读2分钟

跟着来,你也可以手写VueRouter - 掘金 (juejin.cn)

这次是参考这位大佬的文章,尝试跟着写。文章很长,慢慢啃。 我个人是比较喜欢这种循序渐进式的教学文章的,一步步的就像升级打怪,不过如果碰到难题基本上只能自己解决了。

框架版本为vue2,vue-router-3.5.2

主体框架搭建

Vue2

vue create hello-vue-router
  • 选择vue-router
yarn serve
  • 运行

新建文件夹hello-vue-router

image.png

  • 在src目录下新建hello-vue-router文件夹用来存放手写的vue-router
  • 文件夹下新建index.js

更改router/index

  • 在src/router/index.js,更改VueRouter引入路径为
// import VueRouter from 'vue-router'// 原路径
import VueRouter from '@/hello-vue-router/index' // 新路径
  • 先实现hash模式路由,更改mode为hash
const router = new VueRouter({
  mode: 'hash',
  base: process.env.BASE_URL,
  routes
})

手写Vue-router

index.js

  • 新建一个类 VueRouter 并暴露
  • 根据Vue.use()原理,若传入参数是一个对象,Vue.use()会执行参数的install方法,所以需要给VueRouter类添加一个install方法
  • 新建install文件引入
/* 
* @path:src/hello-vue-router/index.js
* @Description: 入口文件 VueRouter类
*/
import { install } from "./install"

export default class VueRouter {
    constructor(options) {

    }
}
VueRouter.install = install

注: 原文写的是class VueRouter(){},应该不加(),正确写法如上

install.js

来到本文第一个难点,install。

  1. Vue.use() 在调用install方法时会将Vue构造函数作为第一个参数,传入install(将传入Vue.use()的arguments第一个参数替换成Vue构造函数)
  2. install接受Vue构造函数,并将其暴露(因为index的VueRouter类会用到Vue构造函数的api,这样可以减小打包体积)

具体操作步骤

  1. 检查是否已经注册过并已拿到Vue构造函数,是则不执行后续操作,避免重复注册
  2. 进行全局Vue混入mixin,每个实例都会被影响
  3. 因为只能在根组件的this.$options里拿到router实例,所以需要在$options构建好的生命周期进行混入,这个生命周期最早就是beforeCreate
Vue.mixin({
        // Vue创建前的钩子函数,此时$options已挂载完成
        beforeCreate() {
            // 通过判断组件实例this.$options有无router属性来判断是否为根实例
            // 只有根实例初始化时我们挂载了VueRouter实例router(main.js中New Vue({router})时
            if (this.$options.router) {
                this._routerRoot = this
                // 在 Vue 根实例添加_router属性(VueRouter 实例)
                this._router = this.$options.router
                this._route = {}
            } else {
                // 为每个组件实例定义_routerRoot,回溯查找_routerRoot
                this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
            }
        }
    })
  1. 判断是否在根组件中,如果this.$options.router存在就表示在根组件中(因为是在main.js中引入的)
  2. 若是根组件就添加_routerRoot属性,这个属性就是VueRoueter实例
  3. 若不是根组件,则向父元素查找_routerRoot属性,若找不到就令其等于this(暂不明确为什么等于this,可能是防止报错?)
  4. 全局代理Vue的$router$route属性,让其返回VueRouter实例对象和定义在根组件的_route(暂时为空对象)
Object.defineProperty(Vue.prototype, "$router", {
        get() {
            return this._routerRoot._router
        }
    })

    Object.defineProperty(Vue.prototype, "$route", {
        get() {
            return this._routerRoot._route
        }
    })
  1. 全局注册组件RouterViewRouterLink
 // 注册全局组件
    Vue.component('RouterView', View)
    Vue.component('RouterLink', Link)
  1. 创建文件夹components,新建文件link.jsview.js,在install.js中引入

image.png

link.js

RouterLink组件,实际上就是a标签,接收传入的to属性值

  • to的值可以是字符串或对象,是字符串时直接使用,是对象时取其中的path属性值
  • router在此时已经是代理好的VueRouter实例了
  • render函数的返回值是h函数的调用,h函数既为一个createElement创建函数,作用是创建一个VNode
  • h函数接收三个参数('html标签名',attribute属性值,子节点)
// link组件类似a标签,组件接受一个to参数
/* 
* @path: src/hello-vue-router/components/link.js
* @Description: router-link
*/
export default {
    name: 'RouterLink',
    props: {
        to: {
            type: [String, Object],
            require: true
        }
    },
    render(h) {
        const href = typeof this.to === 'string' ? this.to : this.to.path
        const router = this.$router
        let data = {
            attrs: {
                href: router.mode === 'hash' ? '#' + href : href
            }
        }
        return h('a', data, this.$slots.default)
    }
}

view.js

RouterView组件是一个占位符,这里使用函数式组件先看看效果。

/* 
* @path: src/hello-vue-router/components/view.js
* @Description: router-view
*/

export default {
    name: 'RouterView',
    function: true, // 函数式组件
    render(h) {
        return h('div', 'This is RoutePage')
    }
}

效果

可以看到view的内容,也可以切换路由了。

QQ录屏20220502020718[00_00_01--00_00_21].gif 但是url还是没有带#号,暂不知原因,今日笔记先到这里,如有兴趣欢迎讨论。