Vue-Router原理

140 阅读2分钟

最近在整理以前收藏的学习内容,发现了别人写的这个 vue-router 原理,自己也动手敲了一遍,做个记录
vue-router 之前,确定需求;
要实现的功能

  1. 所有子组件可以访问 $router$route
  2. 实现 router-viewrouter-link
  3. 实现 modehash history
  4. 实现路由的响应式变化

要想实现以上的需求,就需要对 vue-router的结构有个清晰的认知

vue-router 在 main.js中的使用

//main.js
import router from './router'
new Vue({
  router, // 重点
  render: function (h) { return h(App) }
}).$mount('#app')

vue-router 在 router 中

import Vue from 'vue'
import VueRouter from './TSK'
import Home from '../views/Home.vue'
import About from "../views/About.vue"
Vue.use(VueRouter)
const routes = [
  {
    path: '/home',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: About
  }
];
const router = new VueRouter({
  mode: "hash",
  routes
})
export default router

可以很清楚的看到 export default出去了一个 new VueRouter 实例,也就是需要一个 VueRouter 是一个 class 类,这个类接受 mode模式和 routes数组;
Vue.use(vueRouter) 当使用 vue.use注册插件的时候,可以接收两种方式

  1. 一个对象,里面有一个函数 叫做 install
  2. 一个函数 不论是什么,vue 会把自己 注入到 这个函数里,当做第一个参数;
    前期工作已经做完,开始写代码
  3. 构造 Router 类,接收 mode 和 routes
class MyRouter {
    constructor(options) {
        //容错处理
        this.mode = options.mode || "hash"
        this.routes = options.routes || [];
    }
}
MyRouter.install = function (vue){ 
    
}
export default MyRouter
  1. 把只有在 根实例 才有的 router 分发给自己的子节点(重点)
    //  只有 new vue 里面有 一个 router ,但是其他组件都可以使用
// router 是全局路由器 ,route 是当前页面路由
MyRouter.install = function (vue) {
  vue.mixin({
    beforeCreate() {
      if (this.$options && this.$options.router) {
        // 这个地方巧妙
        // 在自身上挂载了 _root 属性,只有根节点的 $options 上才有 
        // router 属性,把它挂载到 自己的 _router属性上
        this._root = this;
        this._router = this.$options.router;
      } else {
      // 其余节点都不是根节点,没有 options 上的 router,只能父级身上找
      // 子-父节点的创建顺序
      // 父:beforeCreate 子:beforeCreate 子:created 父:created
      //因为混入是在 beforeCreate 所以可以获取到 父节点的 _root
        this._root = this.$parent && this.$parent._root
      }
    // 经过混入,所有子节点都指向了 原始的根节点
    // 为了避免更改,使用 只读属性
      Object.defineProperty(this, "$router", {
        get() {
          return this._root.router
        }
      })
    },
  })
  1. 开始整理 routes routes 中一个 path 和 一个 component 一一对应, 在 vueRouter 中 routes 为了更好的表达父子关系,用了树结构
    但是在内部,为了方便要转换成一个对象,对象的 key 值 是 path, value 值是 component
createMap(routes) {
    return routes.reduce((prev, cur) => {
      prev[cur.path] = cur.component;
      return prev
    }, {})
  }

在 类中的 constructor 中生成 routesMap 这个对象,并且开始 init 初始化

// constructor
 this.routesMap = this.createMap(this.routes)
 this.history = new HistoryRoute();
 this.init()

HistoryRoute (不是重点)

class HistoryRoute {
  constructor() {
    this.current = null
  }
}

init 生成 current 路径

  init() {
    if (this.mode == "hash") {
        // 给一个默认值
      location.hash ? '' : location.hash = "/";
      window.addEventListener("load", () => {
        this.history.current = window.location.hash.slice(1)

      })
      window.addEventListener("hashchange", () => {
        this.history.current = window.location.hash.slice(1)
      })
    } else if (this.mode == "history") {
      location.pathname ? '' : location.pathname = "/";
      window.addEventListener("load", () => {
        this.history.current = window.location.pathname
      })
      window.addEventListener("popstate", () => {
        this.history.current = window.location.pathname
      })
    }
  }
  1. 有了 当前的 path 和映射对象,获取 router-view 要渲染的component
 vue.component("router-view", {
    render(h) {
      let current = this._root._router.history.current
      console.log(this);
      let routeMap = this._root._router.routesMap;
      return h(routeMap[current])
    },
  });
  1. 一个简单的 router-link
vue.component('router-link', {
    props: {
      to: String
    },
    render(h) {
      let mode = this._root._router.mode;
      let to = mode === "hash" ? "#" + this.to : this.to
      return h('a', { attrs: { href: to } }, this.$slots.default)
    }
  })
  1. 最后但是最重要的一点,要把路由变成响应式 vue.util.defineReactive 是一个内部属性方法
//vue.mixin -> beforeCreate
vue.util.defineReactive(this, "xxx", this._router.history)

note:通过手写 vue-router 了解到自己对 vue 的认知还是很浅的,还有欠缺一些大型架构的能力,以后要不断的去读源码,去模仿;date:2022.6.3 端午节 vueRouterGit地址
B站老师详细解释 date:2022.6.4