淺析 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}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章