<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                >PS:個人筆記,僅供參考,需要深入了解請閱讀參考資料。 [TOC] # 參考資料 [https://ustbhuangyi.github.io/vue-analysis/prepare/directory.html#sfc](https://ustbhuangyi.github.io/vue-analysis/prepare/directory.html#sfc) [https://yuchengkai.cn/docs/frontend/vue.html#%E8%B7%AF%E7%94%B1%E6%B3%A8%E5%86%8C](https://yuchengkai.cn/docs/frontend/vue.html#%E8%B7%AF%E7%94%B1%E6%B3%A8%E5%86%8C) # 例子 看其提供的 API 來進行分析: ```js import Vue from 'vue' import VueRouter from 'vue-router' import App from './App' Vue.use(VueRouter) // 1. 定義(路由)組件。 // 可以從其他文件 import 進來 const Foo = { template: '<div>foo</div>' } const Bar = { template: '<div>bar</div>' } // 2. 定義路由 // 每個路由應該映射一個組件。 其中"component" 可以是 // 通過 Vue.extend() 創建的組件構造器, // 或者,只是一個組件配置對象。 const routes = [ { path: '/foo', component: Foo }, { path: '/bar', component: Bar } ] // 3. 創建 router 實例,然后傳 `routes` 配置 const router = new VueRouter({ routes // (縮寫)相當于 routes: routes }) // 4. 創建和掛載根實例。 // 記得要通過 router 配置參數注入路由, // 從而讓整個應用都有路由功能 const app = new Vue({ el: '#app', render(h) { return h(App) }, router }) ``` # 路由注冊 先從`Vue.use(VueRouter)`說起。 Vue 從它的設計上就是一個漸進式 JavaScript 框架,它本身的核心是解決視圖渲染的問題,其它的能力就通過插件的方式來解決。Vue-Router 就是官方維護的路由插件,在介紹它的注冊實現之前,我們先來分析一下 Vue 通用的插件注冊原理。 Vue 提供了`Vue.use`的全局 API 來注冊這些插件,定義在`vue/src/core/global-api/use.js`中: ```js export function initUse (Vue: GlobalAPI) { Vue.use = function (plugin: Function | Object) { const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) if (installedPlugins.indexOf(plugin) > -1) { return this } const args = toArray(arguments, 1) args.unshift(this) if (typeof plugin.install === 'function') { plugin.install.apply(plugin, args) } else if (typeof plugin === 'function') { plugin.apply(null, args) } installedPlugins.push(plugin) return this } } ``` `Vue.use`接受一個`plugin`參數,并且維護了一個`_installedPlugins`數組,它存儲所有注冊過的`plugin`;接著又會判斷`plugin`有沒有定義`install`方法,如果有的話則調用該方法,并且該方法執行的第一個參數是`Vue`;最后把`plugin`存儲到`installedPlugins`中。 可以看到 Vue 提供的插件注冊機制很簡單,每個插件都需要實現一個靜態的`install`方法,當我們執行`Vue.use`注冊插件的時候,就會執行這個`install`方法,并且在這個`install`方法的第一個參數我們可以拿到`Vue`對象,這樣的好處就是作為插件的編寫方不需要再額外去`import Vue`了。 # 路由安裝 Vue-Router 的入口文件是`src/index.js`,其中定義了`VueRouter`類,也實現了`install`的靜態方法:`VueRouter.install = install`,它的定義在`src/install.js`中: ```js export let _Vue export function install (Vue) { // 確保 install 只調用一次 if (install.installed && _Vue === Vue) return install.installed = true // 把 Vue 賦值給全局變量 _Vue = Vue const isDef = v => v !== undefined const registerInstance = (vm, callVal) => { let i = vm.$options._parentVnode if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) { i(vm, callVal) } } // 混入鉤子函數 Vue.mixin({ beforeCreate () { if (isDef(this.$options.router)) { this._routerRoot = this this._router = this.$options.router this._router.init(this) // 為 _route 屬性實現雙向綁定 Vue.util.defineReactive(this, '_route', this._router.history.current) } else { this._routerRoot = (this.$parent && this.$parent._routerRoot) || this } registerInstance(this, this) }, destroyed () { registerInstance(this) } }) Object.defineProperty(Vue.prototype, '$router', { get () { return this._routerRoot._router } }) Object.defineProperty(Vue.prototype, '$route', { get () { return this._routerRoot._route } }) // 全局注冊 router-link 和 router-view Vue.component('RouterView', View) Vue.component('RouterLink', Link) const strats = Vue.config.optionMergeStrategies strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created } ``` 通過`Vue.use(plugin)`時候,就是在執行`install`方法。`Vue-Router`的`install`方法會給每一個組件注入`beforeCreate`和`destoryed`鉤子函數,在`beforeCreate`做一些私有屬性定義和路由初始化工作. # VueRouter 實例化 VueRouter 的實現是一個類,我們先對它做一個簡單地分析,它的定義在`src/index.js`中: ```js export default class VueRouter { static install: () => void; static version: string; app: any; apps: Array<any>; ready: boolean; readyCbs: Array<Function>; options: RouterOptions; mode: string; history: HashHistory | HTML5History | AbstractHistory; matcher: Matcher; fallback: boolean; beforeHooks: Array<?NavigationGuard>; resolveHooks: Array<?NavigationGuard>; afterHooks: Array<?AfterNavigationHook>; constructor (options: RouterOptions = {}) { this.app = null this.apps = [] this.options = options this.beforeHooks = [] this.resolveHooks = [] this.afterHooks = [] // 路由匹配對象 this.matcher = createMatcher(options.routes || [], this) // 根據 mode 采取不同的路由方式 let mode = options.mode || 'hash' this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false if (this.fallback) { mode = 'hash' } if (!inBrowser) { mode = 'abstract' } this.mode = mode switch (mode) { case 'history': this.history = new HTML5History(this, options.base) break case 'hash': this.history = new HashHistory(this, options.base, this.fallback) break case 'abstract': this.history = new AbstractHistory(this, options.base) break default: if (process.env.NODE_ENV !== 'production') { assert(false, `invalid mode: ${mode}`) } } } ... ``` 在實例化 VueRouter 的過程中,核心是創建一個路由匹配對象,并且根據 mode 來采取不同的路由方式。 ## 路由匹配對象(matcher) `matcher`相關的實現都在`src/create-matcher.js`中,我們先來看一下`matcher`的數據結構: ```js export type Matcher = { match: (raw: RawLocation, current?: Route, redirectedFrom?: Location) => Route; addRoutes: (routes: Array<RouteConfig>) => void; }; ``` `Matcher`返回了 2 個方法,`match`和`addRoutes`,`match`方法,顧名思義它是做匹配,那么匹配的是什么,在介紹之前,我們先了解路由中重要的 2 個概念,`Loaction`和`Route`,它們的數據結構定義在`flow/declarations.js`中。 - Location ``` declare type Location = { _normalized?: boolean; name?: string; path?: string; hash?: string; query?: Dictionary<string>; params?: Dictionary<string>; append?: boolean; replace?: boolean; } ``` Vue-Router 中定義的`Location`數據結構和瀏覽器提供的`window.location`部分結構有點類似,它們都是對`url`的結構化描述。舉個例子:`/abc?foo=bar&baz=qux#hello`,它的`path`是`/abc`,`query`是`{foo:'bar',baz:'qux'}`。 - Route ```js eclare type Route = { path: string; name: ?string; hash: string; query: Dictionary<string>; params: Dictionary<string>; fullPath: string; matched: Array<RouteRecord>; redirectedFrom?: string; meta?: any; } ``` `Route`表示的是路由中的一條線路,它除了描述了類似`Loctaion`的`path`、`query`、`hash`這些概念,還有`matched`表示匹配到的所有的`RouteRecord`。 ### createMatcher ```js export function createMatcher( routes: Array<RouteConfig>, router: VueRouter ): Matcher { // 創建路由映射表 const { pathList, pathMap, nameMap } = createRouteMap(routes) function addRoutes(routes) { createRouteMap(routes, pathList, pathMap, nameMap) } // 路由匹配 function match( raw: RawLocation, currentRoute?: Route, redirectedFrom?: Location ): Route { //... } return { match, addRoutes } } ``` `createMatcher`函數的作用就是創建路由映射表,然后通過閉包的方式讓`addRoutes`和`match`函數能夠使用路由映射表的幾個對象,最后返回一個`Matcher`對象。 <br /> `createMathcer`首先執行的邏輯是`const { pathList, pathMap, nameMap } = createRouteMap(routes)`創建一個路由映射表,`createRouteMap`的定義在`src/create-route-map`中: ```js export function createRouteMap ( routes: Array<RouteConfig>, oldPathList?: Array<string>, oldPathMap?: Dictionary<RouteRecord>, oldNameMap?: Dictionary<RouteRecord> ): { pathList: Array<string>; pathMap: Dictionary<RouteRecord>; nameMap: Dictionary<RouteRecord>; } { const pathList: Array<string> = oldPathList || [] const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null) const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null) routes.forEach(route => { addRouteRecord(pathList, pathMap, nameMap, route) // 為每一個 route 生成 RouteRecord }) for (let i = 0, l = pathList.length; i < l; i++) { if (pathList[i] === '*') { pathList.push(pathList.splice(i, 1)[0]) l-- i-- } } return { pathList, // 存儲所有的 path pathMap, // 表示一個 path 到 RouteRecord 的映射關系 nameMap // 表示 name 到 RouteRecord 的映射關系 } } ``` `createRouteMap`函數的目標是把用戶的路由配置轉換成一張路由映射表,它包含 3 個部分,`pathList`存儲所有的`path`,`pathMap`表示一個`path`到`RouteRecord`的映射關系,而`nameMap`表示`name`到`RouteRecord`的映射關系。 `RouteRecord`的數據結構如下: ```js declare type RouteRecord = { path: string; regex: RouteRegExp; components: Dictionary<any>; instances: Dictionary<any>; name: ?string; parent: ?RouteRecord; redirect: ?RedirectOption; matchAs: ?string; beforeEnter: ?NavigationGuard; meta: any; props: boolean | Object | Function | Dictionary<boolean | Object | Function>; } ``` 由于`pathList`、`pathMap`、`nameMap`都是引用類型,所以在遍歷整個`routes`過程中去執行`addRouteRecord`方法,會不斷給他們添加數據。那么經過整個`createRouteMap`方法的執行,我們得到的就是`pathList`、`pathMap`和`nameMap`。其中`pathList`是為了記錄路由配置中的所有`path`,而`pathMap`和`nameMap`都是為了通過`path`和`name`能快速查到對應的`RouteRecord`。(忽略中間的詳細過程) # 路由初始化和路由跳轉 ## 路由初始化 當根組件調用`beforeCreate`鉤子函數時,會執行以下代碼 ```js beforeCreate () { // 只有根組件有 router 屬性,所以根組件初始化時會初始化路由 if (isDef(this.$options.router)) { this._routerRoot = this this._router = this.$options.router this._router.init(this) Vue.util.defineReactive(this, '_route', this._router.history.current) } else { this._routerRoot = (this.$parent && this.$parent._routerRoot) || this } registerInstance(this, this) } ``` 接下來看下路由初始化會做些什么 ```js init(app: any /* Vue component instance */) { // 保存組件實例 this.apps.push(app) // 如果根組件已經有了就返回 if (this.app) { return } this.app = app // 賦值路由模式 const history = this.history // 判斷路由模式,以哈希模式為例 if (history instanceof HTML5History) { history.transitionTo(history.getCurrentLocation()) } else if (history instanceof HashHistory) { // 添加 hashchange 監聽 const setupHashListener = () => { history.setupListeners() } // 路由跳轉 history.transitionTo( history.getCurrentLocation(), setupHashListener, setupHashListener ) } // 該回調會在 transitionTo 中調用 // 對組件的 _route 屬性進行賦值,觸發組件渲染 history.listen(route => { this.apps.forEach(app => { app._route = route }) }) } ``` 在路由初始化時,核心就是進行路由的跳轉,改變 URL 然后渲染對應的組件。接下來來看一下路由是如何進行跳轉的。 ## 路由跳轉 ```js transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) { // 獲取匹配的路由信息 const route = this.router.match(location, this.current) // 確認切換路由 this.confirmTransition(route, () => { // 以下為切換路由成功或失敗的回調 // 更新路由信息,對組件的 _route 屬性進行賦值,觸發組件渲染 // 調用 afterHooks 中的鉤子函數 this.updateRoute(route) // 添加 hashchange 監聽 onComplete && onComplete(route) // 更新 URL this.ensureURL() // 只執行一次 ready 回調 if (!this.ready) { this.ready = true this.readyCbs.forEach(cb => { cb(route) }) } }, err => { // 錯誤處理 if (onAbort) { onAbort(err) } if (err && !this.ready) { this.ready = true this.readyErrorCbs.forEach(cb => { cb(err) }) } }) } ``` 在路由跳轉中,需要先獲取匹配的路由信息,所以先來看下如何獲取匹配的路由信息 ```js function match( raw: RawLocation, currentRoute?: Route, redirectedFrom?: Location ): Route { // 序列化 url // 比如對于該 url 來說 /abc?foo=bar&baz=qux##hello // 會序列化路徑為 /abc // 哈希為 ##hello // 參數為 foo: 'bar', baz: 'qux' const location = normalizeLocation(raw, currentRoute, false, router) const { name } = location // 如果是命名路由,就判斷記錄中是否有該命名路由配置 if (name) { const record = nameMap[name] // 沒找到表示沒有匹配的路由 if (!record) return _createRoute(null, location) const paramNames = record.regex.keys .filter(key => !key.optional) .map(key => key.name) // 參數處理 if (typeof location.params !== 'object') { location.params = {} } if (currentRoute && typeof currentRoute.params === 'object') { for (const key in currentRoute.params) { if (!(key in location.params) && paramNames.indexOf(key) > -1) { location.params[key] = currentRoute.params[key] } } } if (record) { location.path = fillParams( record.path, location.params, `named route "${name}"` ) return _createRoute(record, location, redirectedFrom) } } else if (location.path) { // 非命名路由處理 location.params = {} for (let i = 0; i < pathList.length; i++) { // 查找記錄 const path = pathList[i] const record = pathMap[path] // 如果匹配路由,則創建路由 if (matchRoute(record.regex, location.path, location.params)) { return _createRoute(record, location, redirectedFrom) } } } // 沒有匹配的路由 return _createRoute(null, location) } ``` `createRoute`可以根據`record`和`location`創建出來,最終返回的是一條`Route`路徑,我們之前也介紹過它的數據結構。在 Vue-Router 中,所有的`Route`最終都會通過`createRoute`函數創建,并且它最后是不可以被外部修改的。 <br /> 得到匹配的路由信息后就是做路由跳轉了,即執行`confirmTransition`。其核心就是判斷需要跳轉的路由是否存在于記錄中,然后執行各種導航守衛函數,最后完成 URL 的改變和組件的渲染。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看