最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

關(guān)于vue-router的使用及實(shí)現(xiàn)原理

 更新時(shí)間:2023年06月27日 10:21:28   作者:奔跑的前端er  
這篇文章主要介紹了關(guān)于vue-router的使用及實(shí)現(xiàn)原理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

vue-router使用及實(shí)現(xiàn)原理

前端路由是直接找到與地址匹配的一個(gè)組件或?qū)ο蟛⑵滗秩境鰜?lái)。

變?yōu)g覽器地址而不向服務(wù)器發(fā)出請(qǐng)求有兩種方式:

  • 1. 在地址中加入#以欺騙瀏覽器,地址的改變是由于正在進(jìn)行頁(yè)內(nèi)導(dǎo)航
  • 2. 使用H5的window.history功能,使用URL的Hash來(lái)模擬一個(gè)完整的URL。

當(dāng)打包構(gòu)建應(yīng)用時(shí),Javascript 包會(huì)變得非常大,影響頁(yè)面加載。

如果我們能把不同路由對(duì)應(yīng)的組件分割成不同的代碼塊,然后當(dāng)路由被訪問(wèn)的時(shí)候才加載對(duì)應(yīng)組件,這樣就更加高效了。

圖片來(lái)源網(wǎng)絡(luò)

目錄結(jié)構(gòu)

先來(lái)看看整體的目錄結(jié)構(gòu)

圖片來(lái)源網(wǎng)絡(luò)

和流程相關(guān)的主要需要關(guān)注點(diǎn)的就是 components、history 目錄以及 create-matcher.js、create-route-map.js、index.js、install.js。

下面就從 basic 應(yīng)用入口開(kāi)始來(lái)分析 vue-router 的整個(gè)流程。

import Vue from 'vue'
import VueRouter from 'vue-router'
// 1. 插件
// 安裝 <router-view> and <router-link> 組件
// 且給當(dāng)前應(yīng)用下所有的組件都注入 $router and $route 對(duì)象
Vue.use(VueRouter)
// 2. 定義各個(gè)路由下使用的組件,簡(jiǎn)稱路由組件
const Home = { template: '<div>home</div>' }
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
// 3. 創(chuàng)建 VueRouter 實(shí)例 router
const router = new VueRouter({
  mode: 'history',
  base: __dirname,
  routes: [
    { path: '/', component: Home },
    { path: '/foo', component: Foo },
    { path: '/bar', component: Bar }
  ]
})
// 4. 創(chuàng)建 啟動(dòng)應(yīng)用
// 一定要確認(rèn)注入了 router 
// 在 <router-view> 中將會(huì)渲染路由組件
new Vue({
  router,
  template: `
    <div id="app">
      <h1>Basic</h1>
      <ul>
        <li><router-link to="/">/</router-link></li>
        <li><router-link to="/foo">/foo</router-link></li>
        <li><router-link to="/bar">/bar</router-link></li>
        <router-link tag="li" to="/bar">/bar</router-link>
      </ul>
      <router-view class="view"></router-view>
    </div>
  `
}).$mount('#app')

作為插件

上邊代碼中關(guān)鍵的第 1 步,利用 Vue.js 提供的插件機(jī)制 .use(plugin) 來(lái)安裝 VueRouter,而這個(gè)插件機(jī)制則會(huì)調(diào)用該 plugin 對(duì)象的 install 方法(當(dāng)然如果該 plugin 沒(méi)有該方法的話會(huì)把 plugin 自身作為函數(shù)來(lái)調(diào)用);下邊來(lái)看下 vue-router 這個(gè)插件具體的實(shí)現(xiàn)部分。

VueRouter 對(duì)象是在 src/index.js 中暴露出來(lái)的,這個(gè)對(duì)象有一個(gè)靜態(tài)的 install 方法:

/* @flow */
// 導(dǎo)入 install 模塊
import { install } from './install'
// ...
import { inBrowser, supportsHistory } from './util/dom'
// ...
export default class VueRouter {
// ...
}
// 賦值 install
VueRouter.install = install
// 自動(dòng)使用插件
if (inBrowser && window.Vue) {
  window.Vue.use(VueRouter)
}

可以看到這是一個(gè) Vue.js 插件的經(jīng)典寫法,給插件對(duì)象增加 install 方法用來(lái)安裝插件具體邏輯,同時(shí)在最后判斷下如果是在瀏覽器環(huán)境且存在 window.Vue 的話就會(huì)自動(dòng)使用插件。

install 在這里是一個(gè)單獨(dú)的模塊,繼續(xù)來(lái)看同級(jí)下的 src/install.js 的主要邏輯:

// router-view router-link 組件
import View from './components/view'
import Link from './components/link'
// export 一個(gè) Vue 引用
export let _Vue
// 安裝函數(shù)
export function install (Vue) {
  if (install.installed) return
  install.installed = true
  // 賦值私有 Vue 引用
  _Vue = Vue
  // 注入 $router $route
  Object.defineProperty(Vue.prototype, '$router', {
    get () { return this.$root._router }
  })
  Object.defineProperty(Vue.prototype, '$route', {
    get () { return this.$root._route }
  })
  // beforeCreate mixin
  Vue.mixin({
    beforeCreate () {
      // 判斷是否有 router
      if (this.$options.router) {
        // 賦值 _router
        this._router = this.$options.router
        // 初始化 init
        this._router.init(this)
        // 定義響應(yīng)式的 _route 對(duì)象
        Vue.util.defineReactive(this, '_route', this._router.history.current)
      }
    }
  })
  // 注冊(cè)組件
  Vue.component('router-view', View)
  Vue.component('router-link', Link)
// ...
}

這里就會(huì)有一些疑問(wèn)了? 

為啥要 export 一個(gè) Vue 引用?

插件在打包的時(shí)候是肯定不希望把 vue 作為一個(gè)依賴包打進(jìn)去的,但是呢又希望使用 Vue 對(duì)象本身的一些方法,此時(shí)就可以采用上邊類似的做法,在 install 的時(shí)候把這個(gè)變量賦值 Vue ,這樣就可以在其他地方使用 Vue 的一些方法而不必引入 vue 依賴包(前提是保證 install 后才會(huì)使用)。

通過(guò)給 Vue.prototype 定義 $router、$route 屬性就可以把他們注入到所有組件中嗎?

在 Vue.js 中所有的組件都是被擴(kuò)展的 Vue 實(shí)例,也就意味著所有的組件都可以訪問(wèn)到這個(gè)實(shí)例原型上定義的屬性。

beforeCreate mixin 這個(gè)在后邊創(chuàng)建 Vue 實(shí)例的時(shí)候再細(xì)說(shuō)。

實(shí)例化 VueRouter

在入口文件中,首先要實(shí)例化一個(gè) VueRouter ,然后將其傳入 Vue 實(shí)例的 options 中。

現(xiàn)在繼續(xù)來(lái)看在 src/index.js 中暴露出來(lái)的 VueRouter 類:

// ...
import { createMatcher } from './create-matcher'
// ...
export default class VueRouter {
// ...
  constructor (options: RouterOptions = {}) {
    this.app = null
    this.options = options
    this.beforeHooks = []
    this.afterHooks = []
    // 創(chuàng)建 match 匹配函數(shù)
    this.match = createMatcher(options.routes || [])
    // 根據(jù) mode 實(shí)例化具體的 History
    let mode = options.mode || 'hash'
    this.fallback = mode === 'history' && !supportsHistory
    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)
        break
      default:
        assert(false, `invalid mode: ${mode}`)
    }
  }
// ...
}

里邊包含了重要的一步:創(chuàng)建 match 匹配函數(shù)。

match 匹配函數(shù)

匹配函數(shù)是由 src/create-matcher.js 中的 createMatcher 創(chuàng)建的:

/* @flow */
import Regexp from 'path-to-regexp'
// ...
import { createRouteMap } from './create-route-map'
// ...
export function createMatcher (routes: Array<RouteConfig>): Matcher {
  // 創(chuàng)建路由 map
  const { pathMap, nameMap } = createRouteMap(routes)
  // 匹配函數(shù)
  function match (
    raw: RawLocation,
    currentRoute?: Route,
    redirectedFrom?: Location
  ): Route {
// ...
  }
  function redirect (
    record: RouteRecord,
    location: Location
  ): Route {
// ...
  }
  function alias (
    record: RouteRecord,
    location: Location,
    matchAs: string
  ): Route {
// ...
  }
  function _createRoute (
    record: ?RouteRecord,
    location: Location,
    redirectedFrom?: Location
  ): Route {
    if (record && record.redirect) {
      return redirect(record, redirectedFrom || location)
    }
    if (record && record.matchAs) {
      return alias(record, location, record.matchAs)
    }
    return createRoute(record, location, redirectedFrom)
  }
  // 返回
  return match
}
// ...

具體邏輯后續(xù)再具體分析,現(xiàn)在只需要理解為根據(jù)傳入的 routes 配置生成對(duì)應(yīng)的路由 map,然后直接返回了 match 匹配函數(shù)。

繼續(xù)來(lái)看 src/create-route-map.js 中的 createRouteMap 函數(shù):

/* @flow */
import { assert, warn } from './util/warn'
import { cleanPath } from './util/path'
// 創(chuàng)建路由 map
export function createRouteMap (routes: Array<RouteConfig>): {
  pathMap: Dictionary<RouteRecord>,
  nameMap: Dictionary<RouteRecord>
} {
  // path 路由 map
  const pathMap: Dictionary<RouteRecord> = Object.create(null)
  // name 路由 map
  const nameMap: Dictionary<RouteRecord> = Object.create(null)
  // 遍歷路由配置對(duì)象 增加 路由記錄
  routes.forEach(route => {
    addRouteRecord(pathMap, nameMap, route)
  })
  return {
    pathMap,
    nameMap
  }
}
// 增加 路由記錄 函數(shù)
function addRouteRecord (
  pathMap: Dictionary<RouteRecord>,
  nameMap: Dictionary<RouteRecord>,
  route: RouteConfig,
  parent?: RouteRecord,
  matchAs?: string
) {
  // 獲取 path 、name
  const { path, name } = route
  assert(path != null, `"path" is required in a route configuration.`)
  // 路由記錄 對(duì)象
  const record: RouteRecord = {
    path: normalizePath(path, parent),
    components: route.components || { default: route.component },
    instances: {},
    name,
    parent,
    matchAs,
    redirect: route.redirect,
    beforeEnter: route.beforeEnter,
    meta: route.meta || {}
  }
  // 嵌套子路由 則遞歸增加 記錄
  if (route.children) {
// ...
    route.children.forEach(child => {
      addRouteRecord(pathMap, nameMap, child, record)
    })
  }
  // 處理別名 alias 邏輯 增加對(duì)應(yīng)的 記錄
  if (route.alias !== undefined) {
    if (Array.isArray(route.alias)) {
      route.alias.forEach(alias => {
        addRouteRecord(pathMap, nameMap, { path: alias }, parent, record.path)
      })
    } else {
      addRouteRecord(pathMap, nameMap, { path: route.alias }, parent, record.path)
    }
  }
  // 更新 path map
  pathMap[record.path] = record
  // 更新 name map
  if (name) {
    if (!nameMap[name]) {
      nameMap[name] = record
    } else {
      warn(false, `Duplicate named routes definition: { name: "${name}", path: "${record.path}" }`)
    }
  }
}
function normalizePath (path: string, parent?: RouteRecord): string {
  path = path.replace(/\/$/, '')
  if (path[0] === '/') return path
  if (parent == null) return path
  return cleanPath(`${parent.path}/${path}`)
}

可以看出主要做的事情就是根據(jù)用戶路由配置對(duì)象生成普通的根據(jù) path 來(lái)對(duì)應(yīng)的路由記錄以及根據(jù) name 來(lái)對(duì)應(yīng)的路由記錄的 map,方便后續(xù)匹配對(duì)應(yīng)。

實(shí)例化 History

這也是很重要的一步,所有的 History 類都是在 src/history/ 目錄下,現(xiàn)在呢不需要關(guān)心具體的每種 History 的具體實(shí)現(xiàn)上差異,只需要知道他們都是繼承自 src/history/base.js 中的 History 類的:

/* @flow */
// ...
import { inBrowser } from '../util/dom'
import { runQueue } from '../util/async'
import { START, isSameRoute } from '../util/route'
// 這里從之前分析過(guò)的 install.js 中 export _Vue
import { _Vue } from '../install'
export class History {
// ...
  constructor (router: VueRouter, base: ?string) {
    this.router = router
    this.base = normalizeBase(base)
    // start with a route object that stands for "nowhere"
    this.current = START
    this.pending = null
  }
// ...
}
// 得到 base 值
function normalizeBase (base: ?string): string {
  if (!base) {
    if (inBrowser) {
      // respect <base> tag
      const baseEl = document.querySelector('base')
      base = baseEl ? baseEl.getAttribute('href') : '/'
    } else {
      base = '/'
    }
  }
  // make sure there's the starting slash
  if (base.charAt(0) !== '/') {
    base = '/' + base
  }
  // remove trailing slash
  return base.replace(/\/$/, '')
}
// ...

實(shí)例化完了 VueRouter,下邊就該看看 Vue 實(shí)例了。

實(shí)例化 Vue

new Vue({
  router,
  template: `
    <div id="app">
      <h1>Basic</h1>
      <ul>
        <li><router-link to="/">/</router-link></li>
        <li><router-link to="/foo">/foo</router-link></li>
        <li><router-link to="/bar">/bar</router-link></li>
        <router-link tag="li" to="/bar">/bar</router-link>
      </ul>
      <router-view class="view"></router-view>
    </div>
  `
}).$mount('#app')

options 中傳入了 router,以及模板;還記得上邊沒(méi)具體分析的 beforeCreate mixin 嗎,此時(shí)創(chuàng)建一個(gè) Vue 實(shí)例,對(duì)應(yīng)的 beforeCreate 鉤子就會(huì)被調(diào)用:

// ...
  Vue.mixin({
    beforeCreate () {
      // 判斷是否有 router
      if (this.$options.router) {
        // 賦值 _router
        this._router = this.$options.router
        // 初始化 init
        this._router.init(this)
        // 定義響應(yīng)式的 _route 對(duì)象
        Vue.util.defineReactive(this, '_route', this._router.history.current)
      }
    }
  })

具體來(lái)說(shuō),首先判斷實(shí)例化時(shí) options 是否包含 router,如果包含也就意味著是一個(gè)帶有路由配置的實(shí)例被創(chuàng)建了,此時(shí)才有必要繼續(xù)初始化路由相關(guān)邏輯。

然后給當(dāng)前實(shí)例賦值_router,這樣在訪問(wèn)原型上的 $router 的時(shí)候就可以得到 router 了。

下邊來(lái)看里邊兩個(gè)關(guān)鍵:router.init 和 定義響應(yīng)式的 _route 對(duì)象。

router.init

然后來(lái)看 router 的 init 方法就干了哪些事情,依舊是在 src/index.js 中:

/* @flow */
import { install } from './install'
import { createMatcher } from './create-matcher'
import { HashHistory, getHash } from './history/hash'
import { HTML5History, getLocation } from './history/html5'
import { AbstractHistory } from './history/abstract'
import { inBrowser, supportsHistory } from './util/dom'
import { assert } from './util/warn'
export default class VueRouter {
// ...
  init (app: any /* Vue component instance */) {
// ...
    this.app = app
    const history = this.history
    if (history instanceof HTML5History) {
      history.transitionTo(getLocation(history.base))
    } else if (history instanceof HashHistory) {
      history.transitionTo(getHash(), () => {
        window.addEventListener('hashchange', () => {
          history.onHashChange()
        })
      })
    }
    history.listen(route => {
      this.app._route = route
    })
  }
// ...
}
// ...

可以看到初始化主要就是給 app 賦值,針對(duì)于 HTML5History 和 HashHistory 特殊處理,因?yàn)樵谶@兩種模式下才有可能存在進(jìn)入時(shí)候的不是默認(rèn)頁(yè),需要根據(jù)當(dāng)前瀏覽器地址欄里的 path 或者 hash 來(lái)激活對(duì)應(yīng)的路由,此時(shí)就是通過(guò)調(diào)用 transitionTo 來(lái)達(dá)到目的;而且此時(shí)還有個(gè)注意點(diǎn)是針對(duì)于 HashHistory 有特殊處理,為什么不直接在初始化 HashHistory 的時(shí)候監(jiān)聽(tīng) hashchange 事件呢?

這個(gè)是為了修復(fù)vuejs/vue-router#725這個(gè) bug 而這樣做的,簡(jiǎn)要來(lái)說(shuō)就是說(shuō)如果在 beforeEnter 這樣的鉤子函數(shù)中是異步的話,beforeEnter 鉤子就會(huì)被觸發(fā)兩次,原因是因?yàn)樵诔跏蓟臅r(shí)候如果此時(shí)的 hash 值不是以 / 開(kāi)頭的話就會(huì)補(bǔ)上 #/,這個(gè)過(guò)程會(huì)觸發(fā) hashchange 事件,所以會(huì)再走一次生命周期鉤子,也就意味著會(huì)再次調(diào)用 beforeEnter 鉤子函數(shù)。

來(lái)看看這個(gè)具體的 transitionTo 方法的大概邏輯,在 src/history/base.js 中:

/* @flow */
import type VueRouter from '../index'
import { warn } from '../util/warn'
import { inBrowser } from '../util/dom'
import { runQueue } from '../util/async'
import { START, isSameRoute } from '../util/route'
import { _Vue } from '../install'
export class History {
// ...
  transitionTo (location: RawLocation, cb?: Function) {
    // 調(diào)用 match 得到匹配的 route 對(duì)象
    const route = this.router.match(location, this.current)
    // 確認(rèn)過(guò)渡
    this.confirmTransition(route, () => {
      // 更新當(dāng)前 route 對(duì)象
      this.updateRoute(route)
      cb && cb(route)
      // 子類實(shí)現(xiàn)的更新url地址
      // 對(duì)于 hash 模式的話 就是更新 hash 的值
      // 對(duì)于 history 模式的話 就是利用 pushstate / replacestate 來(lái)更新
      // 瀏覽器地址
      this.ensureURL()
    })
  }
  // 確認(rèn)過(guò)渡
  confirmTransition (route: Route, cb: Function) {
    const current = this.current
    // 如果是相同 直接返回
    if (isSameRoute(route, current)) {
      this.ensureURL()
      return
    }
    // 交叉比對(duì)當(dāng)前路由的路由記錄和現(xiàn)在的這個(gè)路由的路由記錄
    // 以便能準(zhǔn)確得到父子路由更新的情況下可以確切的知道
    // 哪些組件需要更新 哪些不需要更新
    const {
      deactivated,
      activated
    } = resolveQueue(this.current.matched, route.matched)
    // 整個(gè)切換周期的隊(duì)列
    const queue: Array<?NavigationGuard> = [].concat(
      // leave 的鉤子
      extractLeaveGuards(deactivated),
      // 全局 router before hooks
      this.router.beforeHooks,
      // 將要更新的路由的 beforeEnter 鉤子
      activated.map(m => m.beforeEnter),
      // 異步組件
      resolveAsyncComponents(activated)
    )
    this.pending = route
    每一個(gè)隊(duì)列執(zhí)行的 iterator 函數(shù)
    const iterator = (hook: NavigationGuard, next) => {
      // 確保期間還是當(dāng)前路由
      if (this.pending !== route) return
      hook(route, current, (to: any) => {
        if (to === false) {
          // next(false) -> abort navigation, ensure current URL
          this.ensureURL(true)
        } else if (typeof to === 'string' || typeof to === 'object') {
          // next('/') or next({ path: '/' }) -> redirect
          this.push(to)
        } else {
          // confirm transition and pass on the value
          next(to)
        }
      })
    }
    // 執(zhí)行隊(duì)列
    runQueue(queue, iterator, () => {
      const postEnterCbs = []
      // 組件內(nèi)的鉤子
      const enterGuards = extractEnterGuards(activated, postEnterCbs, () => {
        return this.current === route
      })
      // 在上次的隊(duì)列執(zhí)行完成后再執(zhí)行組件內(nèi)的鉤子
      // 因?yàn)樾枰犬惒浇M件以及是OK的情況下才能執(zhí)行
      runQueue(enterGuards, iterator, () => {
        // 確保期間還是當(dāng)前路由
        if (this.pending === route) {
          this.pending = null
          cb(route)
          this.router.app.$nextTick(() => {
            postEnterCbs.forEach(cb => cb())
          })
        }
      })
    })
  }
  // 更新當(dāng)前 route 對(duì)象
  updateRoute (route: Route) {
    const prev = this.current
    this.current = route
    // 注意 cb 的值 
    // 每次更新都會(huì)調(diào)用 下邊需要用到!
    this.cb && this.cb(route)
    // 執(zhí)行 after hooks 回調(diào)
    this.router.afterHooks.forEach(hook => {
      hook && hook(route, prev)
    })
  }
}
// ...

可以看到整個(gè)過(guò)程就是執(zhí)行約定的各種鉤子以及處理異步組件問(wèn)題,這里有一些具體函數(shù)具體細(xì)節(jié)被忽略掉了(后續(xù)會(huì)具體分析)但是不影響具體理解這個(gè)流程。

但是需要注意一個(gè)概念:路由記錄,每一個(gè)路由 route 對(duì)象都對(duì)應(yīng)有一個(gè) matched 屬性,它對(duì)應(yīng)的就是路由記錄,他的具體含義在調(diào)用 match() 中有處理;通過(guò)之前的分析可以知道這個(gè) match 是在 src/create-matcher.js 中的:

// ...
import { createRoute } from './util/route'
import { createRouteMap } from './create-route-map'
// ...
export function createMatcher (routes: Array<RouteConfig>): Matcher {
  const { pathMap, nameMap } = createRouteMap(routes)
  // 關(guān)鍵的 match
  function match (
    raw: RawLocation,
    currentRoute?: Route,
    redirectedFrom?: Location
  ): Route {
    const location = normalizeLocation(raw, currentRoute)
    const { name } = location
    // 命名路由處理
    if (name) {
      // nameMap[name] = 路由記錄
      const record = nameMap[name]
      const paramNames = getParams(record.path)
// ...
      if (record) {
        location.path = fillParams(record.path, location.params, `named route "${name}"`)
        // 創(chuàng)建 route
        return _createRoute(record, location, redirectedFrom)
      }
    } else if (location.path) {
      // 普通路由處理
      location.params = {}
      for (const path in pathMap) {
        if (matchRoute(path, location.params, location.path)) {
          // 匹配成功 創(chuàng)建route
          // pathMap[path] = 路由記錄
          return _createRoute(pathMap[path], location, redirectedFrom)
        }
      }
    }
    // no match
    return _createRoute(null, location)
  }
// ...
  // 創(chuàng)建路由
  function _createRoute (
    record: ?RouteRecord,
    location: Location,
    redirectedFrom?: Location
  ): Route {
    // 重定向和別名邏輯
    if (record && record.redirect) {
      return redirect(record, redirectedFrom || location)
    }
    if (record && record.matchAs) {
      return alias(record, location, record.matchAs)
    }
    // 創(chuàng)建路由對(duì)象
    return createRoute(record, location, redirectedFrom)
  }
  return match
}
// ...

路由記錄在分析 match 匹配函數(shù)那里以及分析過(guò)了,這里還需要了解下創(chuàng)建路由對(duì)象的 createRoute,存在于 src/util/route.js 中:

// ...
export function createRoute (
  record: ?RouteRecord,
  location: Location,
  redirectedFrom?: Location
): Route {
  // 可以看到就是一個(gè)被凍結(jié)的普通對(duì)象
  const route: Route = {
    name: location.name || (record && record.name),
    meta: (record && record.meta) || {},
    path: location.path || '/',
    hash: location.hash || '',
    query: location.query || {},
    params: location.params || {},
    fullPath: getFullPath(location),
    // 根據(jù)記錄層級(jí)的得到所有匹配的 路由記錄
    matched: record ? formatMatch(record) : []
  }
  if (redirectedFrom) {
    route.redirectedFrom = getFullPath(redirectedFrom)
  }
  return Object.freeze(route)
}
// ...
function formatMatch (record: ?RouteRecord): Array<RouteRecord> {
  const res = []
  while (record) {
    res.unshift(record)
    record = record.parent
  }
  return res
}
// ...

回到之前看的 init,最后調(diào)用了 history.listen 方法:

history.listen(route => {
  this.app._route = route
})

listen 方法很簡(jiǎn)單就是設(shè)置下當(dāng)前歷史對(duì)象的 cb 的值, 在之前分析 transitionTo 的時(shí)候已經(jīng)知道在 history 更新完畢的時(shí)候調(diào)用下這個(gè) cb。

然后看這里設(shè)置的這個(gè)函數(shù)的作用就是更新下當(dāng)前應(yīng)用實(shí)例的 _route 的值,更新這個(gè)有什么用呢?請(qǐng)看下段落的分析。

defineReactive 定義 _route

繼續(xù)回到 beforeCreate 鉤子函數(shù)中,在最后通過(guò) Vue 的工具方法給當(dāng)前應(yīng)用實(shí)例定義了一個(gè)響應(yīng)式的 _route 屬性,值就是獲取的 this._router.history.current,也就是當(dāng)前 history 實(shí)例的當(dāng)前活動(dòng)路由對(duì)象。

給應(yīng)用實(shí)例定義了這么一個(gè)響應(yīng)式的屬性值也就意味著如果該屬性值發(fā)生了變化,就會(huì)觸發(fā)更新機(jī)制,繼而調(diào)用應(yīng)用實(shí)例的 render 重新渲染。

還記得上一段結(jié)尾留下的疑問(wèn),也就是 history 每次更新成功后都會(huì)去更新應(yīng)用實(shí)例的 _route 的值,也就意味著一旦 history 發(fā)生改變就會(huì)觸發(fā)更新機(jī)制調(diào)用應(yīng)用實(shí)例的 render 方法進(jìn)行重新渲染。

router-link 和 router-view 組件

new Vue({
  router,
  template: `
    <div id="app">
      <h1>Basic</h1>
      <ul>
        <li><router-link to="/">/</router-link></li>
        <li><router-link to="/foo">/foo</router-link></li>
        <li><router-link to="/bar">/bar</router-link></li>
        <router-link tag="li" to="/bar">/bar</router-link>
      </ul>
      <router-view class="view"></router-view>
    </div>
  `
}).$mount('#app')

可以看到這個(gè)實(shí)例的 template 中包含了兩個(gè)自定義組件:router-link 和 router-view。

router-view 組件

router-view 組件比較簡(jiǎn)單,所以這里就先來(lái)分析它,他是在源碼的 src/components/view.js 中定義的:

export default {
  name: 'router-view',
  functional: true, // 功能組件 純粹渲染
  props: {
    name: {
      type: String,
      default: 'default' // 默認(rèn)default 默認(rèn)命名視圖的name
    }
  },
  render (h, { props, children, parent, data }) {
    // 解決嵌套深度問(wèn)題
    data.routerView = true
    // route 對(duì)象
    const route = parent.$route
    // 緩存
    const cache = parent._routerViewCache || (parent._routerViewCache = {})
    let depth = 0
    let inactive = false
    // 當(dāng)前組件的深度
    while (parent) {
      if (parent.$vnode && parent.$vnode.data.routerView) {
        depth++
      }
      處理 keepalive 邏輯
      if (parent._inactive) {
        inactive = true
      }
      parent = parent.$parent
    }
    data.routerViewDepth = depth
    // 得到相匹配的當(dāng)前組件層級(jí)的 路由記錄
    const matched = route.matched[depth]
    if (!matched) {
      return h()
    }
    // 得到要渲染組件
    const name = props.name
    const component = inactive
      ? cache[name]
      : (cache[name] = matched.components[name])
    if (!inactive) {
      // 非 keepalive 模式下 每次都需要設(shè)置鉤子
      // 進(jìn)而更新(賦值&銷毀)匹配了的實(shí)例元素
      const hooks = data.hook || (data.hook = {})
      hooks.init = vnode => {
        matched.instances[name] = vnode.child
      }
      hooks.prepatch = (oldVnode, vnode) => {
        matched.instances[name] = vnode.child
      }
      hooks.destroy = vnode => {
        if (matched.instances[name] === vnode.child) {
          matched.instances[name] = undefined
        }
      }
    }
    // 調(diào)用 createElement 函數(shù) 渲染匹配的組件
    return h(component, data, children)
  }
}

可以看到邏輯還是比較簡(jiǎn)單的,拿到匹配的組件進(jìn)行渲染就可以了。

router-link 組件

再來(lái)看看導(dǎo)航鏈接組件,他在源碼的 src/components/link.js 中定義的:

// ...
import { createRoute, isSameRoute, isIncludedRoute } from '../util/route'
// ...
export default {
  name: 'router-link',
  props: {
    // 傳入的組件屬性們
    to: { // 目標(biāo)路由的鏈接
      type: toTypes,
      required: true
    },
    // 創(chuàng)建的html標(biāo)簽
    tag: {
      type: String,
      default: 'a'
    },
    // 完整模式,如果為 true 那么也就意味著
    // 絕對(duì)相等的路由才會(huì)增加 activeClass
    // 否則是包含關(guān)系
    exact: Boolean,
    // 在當(dāng)前(相對(duì))路徑附加路徑
    append: Boolean,
    // 如果為 true 則調(diào)用 router.replace() 做替換歷史操作
    replace: Boolean,
    // 鏈接激活時(shí)使用的 CSS 類名
    activeClass: String
  },
  render (h: Function) {
    // 得到 router 實(shí)例以及當(dāng)前激活的 route 對(duì)象
    const router = this.$router
    const current = this.$route
    const to = normalizeLocation(this.to, current, this.append)
    // 根據(jù)當(dāng)前目標(biāo)鏈接和當(dāng)前激活的 route匹配結(jié)果
    const resolved = router.match(to, current)
    const fullPath = resolved.redirectedFrom || resolved.fullPath
    const base = router.history.base
    // 創(chuàng)建的 href
    const href = createHref(base, fullPath, router.mode)
    const classes = {}
    // 激活class 優(yōu)先當(dāng)前組件上獲取 要么就是 router 配置的 linkActiveClass
    // 默認(rèn) router-link-active
    const activeClass = this.activeClass || router.options.linkActiveClass || 'router-link-active'
    // 相比較目標(biāo)
    // 因?yàn)橛忻酚?所有不一定有path
    const compareTarget = to.path ? createRoute(null, to) : resolved
    // 如果嚴(yán)格模式的話 就判斷是否是相同路由(path query params hash)
    // 否則就走包含邏輯(path包含,query包含 hash為空或者相同)
    classes[activeClass] = this.exact
      ? isSameRoute(current, compareTarget)
      : isIncludedRoute(current, compareTarget)
    // 事件綁定
    const on = {
      click: (e) => {
        // 忽略帶有功能鍵的點(diǎn)擊
        if (e.metaKey || e.ctrlKey || e.shiftKey) return
        // 已阻止的返回
        if (e.defaultPrevented) return
        // 右擊
        if (e.button !== 0) return
        // `target="_blank"` 忽略
        const target = e.target.getAttribute('target')
        if (/\b_blank\b/i.test(target)) return
        // 阻止默認(rèn)行為 防止跳轉(zhuǎn)
        e.preventDefault()
        if (this.replace) {
          // replace 邏輯
          router.replace(to)
        } else {
          // push 邏輯
          router.push(to)
        }
      }
    }
    // 創(chuàng)建元素需要附加的數(shù)據(jù)們
    const data: any = {
      class: classes
    }
    if (this.tag === 'a') {
      data.on = on
      data.attrs = { href }
    } else {
      // 找到第一個(gè) <a> 給予這個(gè)元素事件綁定和href屬性
      const a = findAnchor(this.$slots.default)
      if (a) {
        // in case the <a> is a static node
        a.isStatic = false
        const extend = _Vue.util.extend
        const aData = a.data = extend({}, a.data)
        aData.on = on
        const aAttrs = a.data.attrs = extend({}, a.data.attrs)
        aAttrs.href = href
      } else {
        // 沒(méi)有 <a> 的話就給當(dāng)前元素自身綁定時(shí)間
        data.on = on
      }
    }
    // 創(chuàng)建元素
    return h(this.tag, data, this.$slots.default)
  }
}
function findAnchor (children) {
  if (children) {
    let child
    for (let i = 0; i < children.length; i++) {
      child = children[i]
      if (child.tag === 'a') {
        return child
      }
      if (child.children && (child = findAnchor(child.children))) {
        return child
      }
    }
  }
}
function createHref (base, fullPath, mode) {
  var path = mode === 'hash' ? '/#' + fullPath : fullPath
  return base ? cleanPath(base + path) : path
}

可以看出 router-link 組件就是在其點(diǎn)擊的時(shí)候根據(jù)設(shè)置的 to 的值去調(diào)用 router 的 push 或者 replace 來(lái)更新路由的,同時(shí)呢,會(huì)檢查自身是否和當(dāng)前路由匹配(嚴(yán)格匹配和包含匹配)來(lái)決定自身的 activeClass 是否添加。

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • vue elementui 動(dòng)態(tài)追加下拉框、輸入框功能

    vue elementui 動(dòng)態(tài)追加下拉框、輸入框功能

    這篇文章主要介紹了vue elementui 動(dòng)態(tài)追加下拉框、輸入框功能,本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2024-04-04
  • 深入了解vue-loader是如何工作的

    深入了解vue-loader是如何工作的

    這篇文章主要為大家詳細(xì)介紹了vue-loader的工作原理與使用,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-12-12
  • Vue-cli3執(zhí)行serve和build命令時(shí)nodejs內(nèi)存溢出問(wèn)題及解決

    Vue-cli3執(zhí)行serve和build命令時(shí)nodejs內(nèi)存溢出問(wèn)題及解決

    這篇文章主要介紹了Vue-cli3執(zhí)行serve和build命令時(shí)nodejs內(nèi)存溢出問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-01-01
  • 使用Vue與WebSocket創(chuàng)建實(shí)時(shí)通知系統(tǒng)

    使用Vue與WebSocket創(chuàng)建實(shí)時(shí)通知系統(tǒng)

    在現(xiàn)代應(yīng)用開(kāi)發(fā)中,實(shí)時(shí)性已成為用戶體驗(yàn)的一個(gè)重要組成部分,ue 作為一款流行的前端框架,配合 WebSocket,可以輕松構(gòu)建實(shí)時(shí)通知系統(tǒng),在本文中,我們將通過(guò)一個(gè)簡(jiǎn)單的示例,使用 Vue 3 的 Composition API(setup 語(yǔ)法糖)來(lái)創(chuàng)建一個(gè)實(shí)時(shí)通知系統(tǒng)
    2024-11-11
  • vue3 自定義指令詳情

    vue3 自定義指令詳情

    這篇文章主要介紹了 vue3 自定義指令詳情,自定義指令分為全局和局部?jī)煞N類型,大家并不陌生。今天我們就講講在 vue3 中,自定義指令定義、使用以及鉤子函數(shù)都有哪些變化?,需要的朋友可以參考一下
    2021-11-11
  • Vue3繪制多系列柱狀圖與曲線圖的示例代碼

    Vue3繪制多系列柱狀圖與曲線圖的示例代碼

    在數(shù)據(jù)可視化領(lǐng)域,圖表是展示復(fù)雜數(shù)據(jù)的重要工具,本文將詳細(xì)介紹如何在Vue3中實(shí)現(xiàn)兩種常見(jiàn)的圖表類型,多系列柱狀圖和堆疊曲線圖,并結(jié)合實(shí)際場(chǎng)景進(jìn)行數(shù)據(jù)展示
    2025-07-07
  • vue-axios的使用以及axios請(qǐng)求賦值為空的問(wèn)題

    vue-axios的使用以及axios請(qǐng)求賦值為空的問(wèn)題

    這篇文章主要介紹了vue-axios的使用以及axios請(qǐng)求賦值為空的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • 詳解vue.js的事件處理器v-on:click

    詳解vue.js的事件處理器v-on:click

    本篇文章主要介紹了詳解vue.js的事件處理器v-on:click,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-06-06
  • Vue3+Element?Plus按需引入(自動(dòng)導(dǎo)入)詳解

    Vue3+Element?Plus按需引入(自動(dòng)導(dǎo)入)詳解

    element-plus根據(jù)官網(wǎng)文檔,推薦用戶采用按需導(dǎo)入的方式進(jìn)行導(dǎo)入,下面這篇文章主要給大家介紹了關(guān)于Vue3+Element?Plus按需引入(自動(dòng)導(dǎo)入)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-10-10
  • vue之?dāng)?shù)據(jù)交互實(shí)例代碼

    vue之?dāng)?shù)據(jù)交互實(shí)例代碼

    本篇文章主要介紹了vue之?dāng)?shù)據(jù)交互實(shí)例代碼,vue中也存在像ajax和jsonp的數(shù)據(jù)交互,實(shí)現(xiàn)向服務(wù)器獲取數(shù)據(jù),有興趣的可以了解一下
    2017-06-06

最新評(píng)論

湄潭县| 化德县| 双流县| 砀山县| 塔河县| 新乡市| 固阳县| 铁岭县| 绥棱县| 抚松县| 宜兴市| 康平县| 确山县| 新晃| 保康县| 中宁县| 赣榆县| 锦州市| 五家渠市| 平江县| 石渠县| 剑河县| 安乡县| 赣榆县| 合作市| 淳化县| 油尖旺区| 武邑县| 乡城县| 济南市| 左云县| 府谷县| 望谟县| 垦利县| 富宁县| 沂源县| 图木舒克市| 抚远县| 南开区| 青州市| 江孜县|