浅析 vue-router 源码和动态路由权限分配

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"背景"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上月立过一个 flag,看完 "},{"type":"codeinline","content":[{"type":"text","text":"vue-router"}]},{"type":"text","text":" 的源码,可到后面逐渐发现 "},{"type":"codeinline","content":[{"type":"text","text":"vue-router"}]},{"type":"text","text":" 的源码并不是像很多总结的文章那么容易理解,阅读过你就会发现里面的很多地方都会有多层的函数调用关系,还有大量的 this 指向问题,而且会有很多辅助函数需要去理解。但还是坚持啃下来了(当然还没看完,内容是真的多),下面是我在政采云(实习)工作闲暇时间阅读源码的一些感悟和总结,并带分析了大三时期使用的 "},{"type":"link","attrs":{"href":"https:\/\/panjiachen.gitee.io\/vue-element-admin-site\/zh\/guide\/#%E5%8A%9F%E8%83%BD","title":"xxx","type":null},"content":[{"type":"text","text":"vue-element-admin"}]},{"type":"text","text":"  这个 vuer 无所不知的后台框架的动态路由权限控制原理。顺便附带本文实践 demo 地址: 基于后台框架开发的 "},{"type":"link","attrs":{"href":"https:\/\/github.com\/251205668\/student-admin-template","title":"xxx","type":null},"content":[{"type":"text","text":"学生管理系统"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"vue-router 源码分析"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/c9\/c96889024d422b90f9731d0d5a8739ff.webp","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先阅读源码之前最好是将 "},{"type":"codeinline","content":[{"type":"text","text":"Vue"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","text":"vue-router"}]},{"type":"text","text":" 的源码克隆下来,然后第一遍阅读建议先跟着 "},{"type":"text","marks":[{"type":"strong"}],"text":"官方文档"},{"type":"text","text":" 先走一遍基础用法,然后第二遍开始阅读源码,先理清楚各层级目录的作用和抽出一些核心的文件出来,过一遍代码的同时写个小的 demo 边看边打断点调试,看不懂没关系,可以边看边参考一些总结的比较好的文章,最后将比较重要的原理过程根据自己的理解整理出来,然后画一画相关的知识脑图加深印象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"前置知识: flow 语法"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JS 在编译过程中可能看不出一些隐蔽的错误,但在运行过程中会报各种各样的 bug。"},{"type":"text","marks":[{"type":"strong"}],"text":"flow"},{"type":"text","text":" 的作用就是编译期间进行静态类型检查,尽早发现错误,抛出异常。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Vue"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"Vue-router"}]},{"type":"text","text":" 等大型项目往往需要这种工具去做静态类型检查以保证代码的可维护性和可靠性。本文所分析的 "},{"type":"codeinline","content":[{"type":"text","text":"vue-router"}]},{"type":"text","text":" 源码中就大量的采用了 flow 去编写函数,所以学习 flow 的语法是有必要的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先安装 flow 环境,初始化环境"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"npm install flow-bin -g\nflow init\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 "},{"type":"codeinline","content":[{"type":"text","text":"index.js"}]},{"type":"text","text":" 中输入这一段报错的代码"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"\/*@flow*\/\nfunction add(x: string, y: number): number {\n return x + y\n}\nadd(2, 11)\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在控制台输入 flow,这个时候不出意外就会抛出异常提示,这就是简单的 flow 使用方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"具体用法还需要参考 "},{"type":"link","attrs":{"href":"https:\/\/flow.org\/en\/docs\/types\/primitives\/","title":"xxx","type":null},"content":[{"type":"text","text":"flow官网 "}]},{"type":"text","text":",另外这种语法是类似于 "},{"type":"link","attrs":{"href":"https:\/\/www.typescriptlang.org\/","title":"xxx","type":null},"content":[{"type":"text","text":"TypeScript"}]},{"type":"text","text":"  的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"注册"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/68\/68a8f1e283748c19f188c283e6350e4b.webp","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们平时在使用 "},{"type":"codeinline","content":[{"type":"text","text":"vue-router"}]},{"type":"text","text":" 的时候通常需要在 "},{"type":"codeinline","content":[{"type":"text","text":"main.js"}]},{"type":"text","text":" 中初始化 "},{"type":"codeinline","content":[{"type":"text","text":"Vue"}]},{"type":"text","text":" 实例时将 "},{"type":"codeinline","content":[{"type":"text","text":"vue-router"}]},{"type":"text","text":" 实例对象当做参数传入"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"例如:"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"import Router from 'vue-router'\nVue.use(Router)\nconst routes = [\n   {\n     path: '\/student',\n    name: 'student',\n    component: Layout,\n    meta: { title: '学生信息查询', icon: 'documentation', roles: ['student'] },\n    children: [\n      {\n        path: 'info',\n        component: () => import('@\/views\/student\/info'),\n        name: 'studentInfo',\n        meta: { title: '信息查询', icon: 'form' }\n      },\n      {\n        path: 'score',\n        component: () => import('@\/views\/student\/score'),\n        name: 'studentScore',\n        meta: { title: '成绩查询', icon: 'score' }\n      }\n    ]\n  }\n  ...\n];\nconst router = new Router({\n  mode: \"history\",\n  linkActiveClass: \"active\",\n  base: process.env.BASE_URL,\n  routes\n});\nnew Vue({\n    router,\n    store,\n    render: h => h(App)\n}).$mount(\"#app\");"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Vue.use"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那么 "},{"type":"codeinline","content":[{"type":"text","text":"Vue.use(Router)"}]},{"type":"text","text":" 又在做什么事情呢"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"问题定位到 "},{"type":"codeinline","content":[{"type":"text","text":"Vue"}]},{"type":"text","text":" 源码中的 "},{"type":"codeinline","content":[{"type":"text","text":"src\/core\/global-api\/use.js"}]},{"type":"text","text":" "},{"type":"text","marks":[{"type":"strong"}],"text":"源码地址"},{"type":"text","text":" (https:\/\/github.com\/vuejs\/vue\/blob\/dev\/src\/core\/global-api\/use.js)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"export function initUse (Vue: GlobalAPI) {\n  Vue.use = function (plugin: Function | Object) {\n    \/\/ 拿到 installPlugins \n    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))\n    \/\/ 保证不会重复注册\n    if (installedPlugins.indexOf(plugin) > -1) {\n      return this\n    }\n    \/\/ 获取第一个参数 plugins 以外的参数\n    const args = toArray(arguments, 1)\n    \/\/ 将 Vue 实例添加到参数\n    args.unshift(this)\n    \/\/ 执行 plugin 的 install 方法 每个 insatll 方法的第一个参数都会变成 Vue,不需要额外引入\n    if (typeof plugin.install === 'function') {\n      plugin.install.apply(plugin, args)\n    } else if (typeof plugin === 'function') {\n      plugin.apply(null, args)\n    }\n    \/\/ 最后用 installPlugins 保存 \n    installedPlugins.push(plugin)\n    return this\n  }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到 "},{"type":"codeinline","content":[{"type":"text","text":"Vue"}]},{"type":"text","text":" 的 "},{"type":"codeinline","content":[{"type":"text","text":"use"}]},{"type":"text","text":" 方法会接受一个 "},{"type":"codeinline","content":[{"type":"text","text":"plugin"}]},{"type":"text","text":" 参数,然后使用 "},{"type":"codeinline","content":[{"type":"text","text":"installPlugins"}]},{"type":"text","text":" 数组 保存已经注册过的 "},{"type":"codeinline","content":[{"type":"text","text":"plugin"}]},{"type":"text","text":"。首先保证 "},{"type":"codeinline","content":[{"type":"text","text":"plugin"}]},{"type":"text","text":" 不被重复注册,然后将 "},{"type":"codeinline","content":[{"type":"text","text":"Vue"}]},{"type":"text","text":" 从函数参数中取出,将整个 "},{"type":"codeinline","content":[{"type":"text","text":"Vue"}]},{"type":"text","text":" 作为 "},{"type":"codeinline","content":[{"type":"text","text":"plugin"}]},{"type":"text","text":" 的"},{"type":"codeinline","content":[{"type":"text","text":"install"}]},{"type":"text","text":" 方法的第一个参数,这样做的好处就是不需要麻烦的另外引入 "},{"type":"codeinline","content":[{"type":"text","text":"Vue"}]},{"type":"text","text":",便于操作。接着就去判断 "},{"type":"codeinline","content":[{"type":"text","text":"plugin"}]},{"type":"text","text":" 上是否存在 "},{"type":"codeinline","content":[{"type":"text","text":"install"}]},{"type":"text","text":" 方法。存在则将赋值后的参数传入执行 ,最后将所有的存在 "},{"type":"codeinline","content":[{"type":"text","text":"install"}]},{"type":"text","text":" 方法的 "},{"type":"codeinline","content":[{"type":"text","text":"plugin"}]},{"type":"text","text":" 交给 "},{"type":"codeinline","content":[{"type":"text","text":"installPlugins"}]},{"type":"text","text":"维护。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"install"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"了解清楚 "},{"type":"codeinline","content":[{"type":"text","text":"Vue.use"}]},{"type":"text","text":" 的结构之后,可以得出 "},{"type":"codeinline","content":[{"type":"text","text":"Vue"}]},{"type":"text","text":" 注册插件其实就是在执行插件的 "},{"type":"codeinline","content":[{"type":"text","text":"install"}]},{"type":"text","text":" 方法,参数的第一项就是 "},{"type":"codeinline","content":[{"type":"text","text":"Vue"}]},{"type":"text","text":",所以我们将代码定位到 "},{"type":"codeinline","content":[{"type":"text","text":"vue-router"}]},{"type":"text","text":" 源码中的 "},{"type":"codeinline","content":[{"type":"text","text":"src\/install.js"}]},{"type":"text","text":" "},{"type":"text","marks":[{"type":"strong"}],"text":"源码地址"},{"type":"text","text":" (https:\/\/github.com\/vuejs\/vue-router\/blob\/dev\/src\/install.js)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"\/\/ 保存 Vue 的局部变量\nexport let _Vue\nexport function install (Vue) {\n  \/\/ 如果已安装\n  if (install.installed && _Vue === Vue) return\n  install.installed = true\n \/\/ 局部变量保留传入的 Vue\n  _Vue = Vue\n  const isDef = v => v !== undefined\n  const registerInstance = (vm, callVal) => {\n    let i = vm.$options._parentVnode\n    if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {\n      i(vm, callVal)\n    }\n  }\n  \/\/ 全局混入钩子函数 每个组件都会有这些钩子函数,执行就会走这里的逻辑\n  Vue.mixin({\n    beforeCreate () {\n      if (isDef(this.$options.router)) {\n        \/\/ new Vue 时传入的根组件 router router对象传入时就可以拿到 this.$options.router\n        \/\/ 根 router\n        this._routerRoot = this\n        this._router = this.$options.router\n        this._router.init(this)\n        \/\/ 变成响应式\n        Vue.util.defineReactive(this, '_route', this._router.history.current)\n      } else {\n        \/\/ 非根组件访问根组件通过$parent\n        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this\n      }\n      registerInstance(this, this)\n    },\n    destroyed () {\n      registerInstance(this)\n    }\n  })\n  \/\/ 原型加入 $router 和 $route\n  Object.defineProperty(Vue.prototype, '$router', {\n    get () { return this._routerRoot._router }\n  })\n  Object.defineProperty(Vue.prototype, '$route', {\n    get () { return this._routerRoot._route }\n  })\n\/\/ 全局注册\n  Vue.component('RouterView', View)\n  Vue.component('RouterLink', Link)\n\/\/ 获取合并策略\n  const strats = Vue.config.optionMergeStrategies\n  \/\/ use the same hook merging strategy for route hooks\n  strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到这段代码核心部分就是在执行 "},{"type":"codeinline","content":[{"type":"text","text":"install"}]},{"type":"text","text":" 方法时使用 "},{"type":"codeinline","content":[{"type":"text","text":"mixin"}]},{"type":"text","text":" 的方式将每个组件都混入 "},{"type":"codeinline","content":[{"type":"text","text":"beforeCreate"}]},{"type":"text","text":","},{"type":"codeinline","content":[{"type":"text","text":"destroyed"}]},{"type":"text","text":" 这两个生命周期钩子。在 "},{"type":"codeinline","content":[{"type":"text","text":"beforeCreate"}]},{"type":"text","text":" 函数中会去判断当前传入的 "},{"type":"codeinline","content":[{"type":"text","text":"router"}]},{"type":"text","text":" 实例是否是根组件,如果是,则将 "},{"type":"codeinline","content":[{"type":"text","text":"_routerRoot"}]},{"type":"text","text":" 赋值为当前组件实例、"},{"type":"codeinline","content":[{"type":"text","text":"_router"}]},{"type":"text","text":" 赋值为传入的"},{"type":"codeinline","content":[{"type":"text","text":"VueRouter"}]},{"type":"text","text":" 实例对象,接着执行 "},{"type":"codeinline","content":[{"type":"text","text":"init"}]},{"type":"text","text":" 方法初始化 "},{"type":"codeinline","content":[{"type":"text","text":"router"}]},{"type":"text","text":",然后将 "},{"type":"codeinline","content":[{"type":"text","text":"this_route"}]},{"type":"text","text":" 响应式化。非根组件的话 "},{"type":"codeinline","content":[{"type":"text","text":"_routerRoot"}]},{"type":"text","text":" 指向 "},{"type":"codeinline","content":[{"type":"text","text":"$parent"}]},{"type":"text","text":" 父实例。然后执行 "},{"type":"codeinline","content":[{"type":"text","text":"registerInstance(this,this)"}]},{"type":"text","text":" 方法,该方法后会,接着原型加入 "},{"type":"codeinline","content":[{"type":"text","text":"$router"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","text":"$route"}]},{"type":"text","text":",最后注册 "},{"type":"codeinline","content":[{"type":"text","text":"RouterView"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","text":"RouterLink"}]},{"type":"text","text":",这就是整个 "},{"type":"codeinline","content":[{"type":"text","text":"install"}]},{"type":"text","text":" 的过程。"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"小结"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Vue.use(plugin)"}]},{"type":"text","text":" 实际上在执行 plugin上的 "},{"type":"codeinline","content":[{"type":"text","text":"install"}]},{"type":"text","text":" 方法,"},{"type":"codeinline","content":[{"type":"text","text":"insatll"}]},{"type":"text","text":" 方法有个重要的步骤:"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用 "},{"type":"codeinline","content":[{"type":"text","text":"mixin"}]},{"type":"text","text":" 在组件中混入 "},{"type":"codeinline","content":[{"type":"text","text":"beforeCreate"}]},{"type":"text","text":" , "},{"type":"codeinline","content":[{"type":"text","text":"destory"}]},{"type":"text","text":" 这俩个生命周期钩子"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 "},{"type":"codeinline","content":[{"type":"text","text":"beforeCreate"}]},{"type":"text","text":" 这个钩子进行初始化。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"全局注册 "},{"type":"codeinline","content":[{"type":"text","text":"router-view"}]},{"type":"text","text":","},{"type":"codeinline","content":[{"type":"text","text":"router-link"}]},{"type":"text","text":"组件"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"VueRouter"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接着就是这个最重要的 "},{"type":"codeinline","content":[{"type":"text","text":"class"}]},{"type":"text","text":" : "},{"type":"codeinline","content":[{"type":"text","text":"VueRouter"}]},{"type":"text","text":"。这一部分代码比较多,所以不一一列举,挑重点分析。"},{"type":"link","attrs":{"href":"https:\/\/github.com\/vuejs\/vue-router\/blob\/v3.1.2\/src\/index.js","title":"xxx","type":null},"content":[{"type":"text","text":"vueRouter源码地址"}]},{"type":"text","text":" 。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"构造函数"}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"  constructor (options: RouterOptions = {}) {\n    this.app  = null\n    this.apps = []\n    \/\/ 传入的配置项\n    this.options = options\n    this.beforeHooks = []\n    this.resolveHooks = []\n    this.afterHooks = []\n    this.matcher = createMatcher(options.routes || [], this)\n    \/\/ 一般分两种模式 hash 和 history 路由 第三种是抽象模式\n    let mode = options.mode || 'hash'\n    \/\/ 判断当前传入的配置是否能使用 history 模式\n    this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false\n    \/\/ 降级处理\n    if (this.fallback) {\n      mode = 'hash'\n    }\n    if (!inBrowser) {\n      mode = 'abstract'\n    }\n    this.mode = mode\n    \/\/ 根据模式实例化不同的 history,history 对象会对路由进行管理 继承于history class\n    switch (mode) {\n      case 'history':\n        this.history = new HTML5History(this, options.base)\n        break\n      case 'hash':\n        this.history = new HashHistory(this, options.base, this.fallback)\n        break\n      case 'abstract':\n        this.history = new AbstractHistory(this, options.base)\n        break\n      default:\n        if (process.env.NODE_ENV !== 'production') {\n          assert(false, `invalid mode: ${mode}`)\n        }\n    }\n  }\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先在初始化 "},{"type":"codeinline","content":[{"type":"text","text":"vueRouter"}]},{"type":"text","text":" 整个对象时定义了许多变量,"},{"type":"codeinline","content":[{"type":"text","text":"app"}]},{"type":"text","text":" 代表 "},{"type":"codeinline","content":[{"type":"text","text":"Vue"}]},{"type":"text","text":" 实例,"},{"type":"codeinline","content":[{"type":"text","text":"options"}]},{"type":"text","text":" 代表传入的配置参数,然后就是路由拦截有用的 "},{"type":"codeinline","content":[{"type":"text","text":"hooks"}]},{"type":"text","text":" 和重要的 "},{"type":"codeinline","content":[{"type":"text","text":"matcher"}]},{"type":"text","text":" (后文会写到)。构造函数其实在做两件事情: 1. 确定当前路由使用的 "},{"type":"codeinline","content":[{"type":"text","text":"mode"}]},{"type":"text","text":";2. 实例化对应的 "},{"type":"codeinline","content":[{"type":"text","text":"history"}]},{"type":"text","text":" 对象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"init"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接着完成实例化 "},{"type":"codeinline","content":[{"type":"text","text":"vueRouter"}]},{"type":"text","text":" 之后,如果这个实例传入后,也就是刚开始说的将 "},{"type":"codeinline","content":[{"type":"text","text":"vueRouter"}]},{"type":"text","text":" 实例在初始化 "},{"type":"codeinline","content":[{"type":"text","text":"Vue"}]},{"type":"text","text":" 时传入,它会在执行 "},{"type":"codeinline","content":[{"type":"text","text":"beforeCreate"}]},{"type":"text","text":" 时执行 "},{"type":"codeinline","content":[{"type":"text","text":"init"}]},{"type":"text","text":" 方法"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"init (app: any) {\n  ...\n  this.apps.push(app)\n  \/\/ 确保后面的逻辑只走一次\n  if (this.app) {\n    return\n  }\n  \/\/ 保存 Vue 实例\n  this.app = app\n  const history = this.history\n  \/\/ 拿到 history 实例之后,调用 transitionTo 进行路由过渡\n  if (history instanceof HTML5History) {\n    history.transitionTo(history.getCurrentLocation())\n  } else if (history instanceof HashHistory) {\n    const setupHashListener = () => {\n      history.setupListeners()\n    }\n    history.transitionTo(\n      history.getCurrentLocation(),\n      setupHashListener,\n      setupHashListener\n    )\n  }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"init"}]},{"type":"text","text":" 方法传入 "},{"type":"codeinline","content":[{"type":"text","text":"Vue"}]},{"type":"text","text":" 实例,保存到 "},{"type":"codeinline","content":[{"type":"text","text":"this.apps"}]},{"type":"text","text":" 当中。"},{"type":"codeinline","content":[{"type":"text","text":"Vue实例"}]},{"type":"text","text":" 会取出当前的 "},{"type":"codeinline","content":[{"type":"text","text":"this.history"}]},{"type":"text","text":",如果是哈希路由,先走 "},{"type":"codeinline","content":[{"type":"text","text":"setupHashListener"}]},{"type":"text","text":" 函数,然后调一个关键的函数 "},{"type":"codeinline","content":[{"type":"text","text":"transitionTo"}]},{"type":"text","text":" 路由过渡,这个函数其实调用了 "},{"type":"codeinline","content":[{"type":"text","text":"this.matcher.match"}]},{"type":"text","text":" 去匹配。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"小结"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先在 "},{"type":"codeinline","content":[{"type":"text","text":"vueRouter"}]},{"type":"text","text":" 构造函数执行完会完成路由模式的选择,生成 "},{"type":"codeinline","content":[{"type":"text","text":"matcher"}]},{"type":"text","text":" ,然后初始化路由需要传入 "},{"type":"codeinline","content":[{"type":"text","text":"vueRouter"}]},{"type":"text","text":" 实例对象,在组件初始化阶段执行 "},{"type":"codeinline","content":[{"type":"text","text":"beforeCreate"}]},{"type":"text","text":" 钩子,调用 "},{"type":"codeinline","content":[{"type":"text","text":"init"}]},{"type":"text","text":" 方法,接着拿到 "},{"type":"codeinline","content":[{"type":"text","text":"this.history"}]},{"type":"text","text":" 去调用 "},{"type":"codeinline","content":[{"type":"text","text":"transitionTo"}]},{"type":"text","text":" 进行路由过渡。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Matcher"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/98\/98edb95e605ac6678a23f9a672637f3e.webp","alt":"Image","title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"之前在 "},{"type":"codeinline","content":[{"type":"text","text":"vueRouter"}]},{"type":"text","text":" 的构造函数中初始化了 "},{"type":"codeinline","content":[{"type":"text","text":"macther"}]},{"type":"text","text":",本节将详细分析下面这句代码到底在做什么事情,以及 "},{"type":"codeinline","content":[{"type":"text","text":"match"}]},{"type":"text","text":" 方法在做什么 "},{"type":"text","marks":[{"type":"strong"}],"text":"源码地址"},{"type":"text","text":" (https:\/\/github.com\/vuejs\/vue-router\/blob\/dev\/src\/create-matcher.js)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" this.matcher = createMatcher(options.routes || [], this)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先将代码定位到"},{"type":"codeinline","content":[{"type":"text","text":"create-matcher.js"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"export function createMatcher (\n  routes: Array,\n  router: VueRouter\n): Matcher {\n  \/\/ 创建映射表\n  const { pathList, pathMap, nameMap } = createRouteMap(routes)\n  \/\/ 添加动态路由\n  function addRoutes(routes){...}\n  \/\/ 计算新路径\n  function match (\n    raw: RawLocation,\n    currentRoute?: Route,\n    redirectedFrom?: Location\n  ): Route {...}\n  \/\/ ... 后面的一些方法暂不展开\n  \n   return {\n    match,\n    addRoutes\n  }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"createMatcher"}]},{"type":"text","text":" 接受俩参数,分别是 "},{"type":"codeinline","content":[{"type":"text","text":"routes"}]},{"type":"text","text":",这个就是我们平时在 "},{"type":"codeinline","content":[{"type":"text","text":"router.js"}]},{"type":"text","text":" 定义的路由表配置,然后还有一个参数是 "},{"type":"codeinline","content":[{"type":"text","text":"router"}]},{"type":"text","text":" 他是 "},{"type":"codeinline","content":[{"type":"text","text":"new vueRouter"}]},{"type":"text","text":" 返回的实例。"},{"type":"codeinline"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"createRouteMap"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面这句代码是在创建一张 "},{"type":"codeinline","content":[{"type":"text","text":"path-record"}]},{"type":"text","text":","},{"type":"codeinline","content":[{"type":"text","text":"name-record"}]},{"type":"text","text":" 的映射表,我们将代码定位到 "},{"type":"codeinline","content":[{"type":"text","text":"create-route-map.js"}]},{"type":"text","text":" "},{"type":"link","attrs":{"href":"https:\/\/github.com\/vuejs\/vue-router\/blob\/dev\/src\/create-route-map.js","title":"xxx","type":null},"content":[{"type":"text","text":"源码地址 "}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"export function createRouteMap (\n  routes: Array,\n  oldPathList?: Array,\n  oldPathMap?: Dictionary,\n  oldNameMap?: Dictionary\n): {\n  pathList: Array,\n  pathMap: Dictionary,\n  nameMap: Dictionary\n} {\n  \/\/ 记录所有的 path\n  const pathList: Array = oldPathList || []\n  \/\/ 记录 path-RouteRecord 的 Map\n  const pathMap: Dictionary = oldPathMap || Object.create(null)\n   \/\/ 记录 name-RouteRecord 的 Map\n  const nameMap: Dictionary = oldNameMap || Object.create(null)\n  \/\/ 遍历所有的 route 生成对应映射表\n  routes.forEach(route => {\n    addRouteRecord(pathList, pathMap, nameMap, route)\n  })\n  \/\/ 调整优先级\n  for (let i = 0, l = pathList.length; i  {\n      const childMatchAs = matchAs\n        ? cleanPath(`${matchAs}\/${child.path}`)\n        : undefined\n      addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)\n    })\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后映射两张表,并将 "},{"type":"codeinline","content":[{"type":"text","text":"record·path"}]},{"type":"text","text":" 保存进 "},{"type":"codeinline","content":[{"type":"text","text":"pathList"}]},{"type":"text","text":","},{"type":"codeinline","content":[{"type":"text","text":"nameMap"}]},{"type":"text","text":" 逻辑相似就不列举了"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"  if (!pathMap[record.path]) {\n    pathList.push(record.path)\n    pathMap[record.path] = record\n  }\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"废了这么大劲将 "},{"type":"codeinline","content":[{"type":"text","text":"pathList"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","text":"pathMap"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","text":"nameMap"}]},{"type":"text","text":" 抽出来是为啥呢? 首先 "},{"type":"codeinline","content":[{"type":"text","text":"pathList"}]},{"type":"text","text":" 是记录路由配置所有的 "},{"type":"codeinline","content":[{"type":"text","text":"path"}]},{"type":"text","text":",然后 "},{"type":"codeinline","content":[{"type":"text","text":"pathMap"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","text":"nameMap"}]},{"type":"text","text":" 方便我们传入 "},{"type":"codeinline","content":[{"type":"text","text":"path"}]},{"type":"text","text":" 或者 "},{"type":"codeinline","content":[{"type":"text","text":"name"}]},{"type":"text","text":" 快速定位到一个 "},{"type":"codeinline","content":[{"type":"text","text":"record"}]},{"type":"text","text":",然后辅助后续路径切换计算路由的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"addRoutes"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这是在 "},{"type":"codeinline","content":[{"type":"text","text":"vue2.2.0"}]},{"type":"text","text":" 之后新添加的 "},{"type":"codeinline","content":[{"type":"text","text":"api"}]},{"type":"text","text":" ,或许很多情况路由并不是写死的,需要动态添加路由。有了前面的 "},{"type":"codeinline","content":[{"type":"text","text":"createRouteMap"}]},{"type":"text","text":" 的基础上我们只需要传入 "},{"type":"codeinline","content":[{"type":"text","text":"routes"}]},{"type":"text","text":" 即可,他就能在原基础上修改"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"function addRoutes (routes) {\n  createRouteMap(routes, pathList, pathMap, nameMap)\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"并且看到在 "},{"type":"codeinline","content":[{"type":"text","text":"createMathcer"}]},{"type":"text","text":" 最后返回了这个方法,所以我们就可以使用这个方法"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"return {\n    match,\n    addRoutes\n  }\n"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"match"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"function match (\n  raw: RawLocation,\n  currentRoute?: Route,\n  redirectedFrom?: Location\n): Route {\n  ...\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接下来就是 "},{"type":"codeinline","content":[{"type":"text","text":"match"}]},{"type":"text","text":" 方法,它接收 3 个参数,其中 "},{"type":"codeinline","content":[{"type":"text","text":"raw"}]},{"type":"text","text":" 是 "},{"type":"codeinline","content":[{"type":"text","text":"RawLocation"}]},{"type":"text","text":" 类型,它可以是一个 "},{"type":"codeinline","content":[{"type":"text","text":"url"}]},{"type":"text","text":" 字符串,也可以是一个 "},{"type":"codeinline","content":[{"type":"text","text":"Location"}]},{"type":"text","text":" 对象;"},{"type":"codeinline","content":[{"type":"text","text":"currentRoute"}]},{"type":"text","text":" 是 "},{"type":"codeinline","content":[{"type":"text","text":"Route"}]},{"type":"text","text":" 类型,它表示当前的路径;"},{"type":"codeinline","content":[{"type":"text","text":"redirectedFrom"}]},{"type":"text","text":" 和重定向相关。"},{"type":"codeinline","content":[{"type":"text","text":"match"}]},{"type":"text","text":" 方法返回的是一个路径,它的作用是根据传入的 "},{"type":"codeinline","content":[{"type":"text","text":"raw"}]},{"type":"text","text":" 和当前的路径 "},{"type":"codeinline","content":[{"type":"text","text":"currentRoute"}]},{"type":"text","text":" 计算出一个新的路径并返回。至于他是如何计算出这条路径的,可以详细看一下如何计算出"},{"type":"codeinline","content":[{"type":"text","text":"location"}]},{"type":"text","text":"的 "},{"type":"codeinline","content":[{"type":"text","text":"normalizeLocation"}]},{"type":"text","text":" 方法和 "},{"type":"codeinline","content":[{"type":"text","text":"_createRoute"}]},{"type":"text","text":" 方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"小结"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"createMatcher"}]},{"type":"text","text":": 根据路由的配置描述建立映射表,包括路径、名称到路由 "},{"type":"codeinline","content":[{"type":"text","text":"record"}]},{"type":"text","text":" 的映射关系, 最重要的就是 "},{"type":"codeinline","content":[{"type":"text","text":"createRouteMap"}]},{"type":"text","text":" 这个方法,这里也是动态路由匹配和嵌套路由的原理。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"addRoutes"}]},{"type":"text","text":": 动态添加路由配置"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"match"}]},{"type":"text","text":": 根据传入的 "},{"type":"codeinline","content":[{"type":"text","text":"raw"}]},{"type":"text","text":" 和当前的路径 "},{"type":"codeinline","content":[{"type":"text","text":"currentRoute"}]},{"type":"text","text":" 计算出一个新的路径并返回。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"horizontalrule"},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"头图:Unsplash"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作者:墨痕"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文:https:\/\/mp.weixin.qq.com\/s\/mf8BFkQvkO13L9QssU8mjA"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文:浅析 vue-router 源码和动态路由权限分配"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"来源:政采云前端团队 - 微信公众号 [ID:Zoo-Team]"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"转载:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章