學習vue-router源碼記錄-2

繼上一遍文章大概介紹了vue-router裏面的概念,這一篇文章主要詳細介紹路由跳轉中發生了什麼。vue

路由跳轉執行的代碼主要在./base.js文件裏,詳細看transitionTo方法。vue-router

transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    const route = this.router.match(location, this.current)
    this.confirmTransition(route, () => {
      this.updateRoute(route)
      onComplete && onComplete(route)
      this.ensureURL()

      // fire ready cbs once
      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) })
      }
    })
  }
複製代碼

transitionTo代碼很是簡單,執行this.route.match,經過比較新的路由和就得路由拿到一個route對象,而後執行confirmTransition確認路由跳轉。bash

下面看看這個route對象的定義app

declare type Route = {
  path: string;
  name: ?string;
  hash: string;
  query: Dictionary<string>;
  params: Dictionary<string>;
  fullPath: string;
  matched: Array<RouteRecord>;
  redirectedFrom?: string;
  meta?: any;
}
複製代碼

對照代碼,咱們主要看matched這個屬性,在上一篇文章裏面已經介紹過RouteRecord對象的定義。路由一開始會執行createMatcher生成一個路由映射表,所以matched裏面存放就是咱們將要跳轉到的路由匹配上的路由配置對象異步

舉一個簡單的例子說明:async

[{
    path: '/parent',
    component: Parent,
    children: [
    { path: 'foo', component: Foo },
    { path: 'bar', component: Bar },
    ],
}]
複製代碼

假設咱們配置瞭如下的路由,createMatcher會生成根據path創建的映射表函數

pathMap = {
    '/parent': RouteRecord,
    '/parent/foo': RouteRecord,
    '/parent/bar': RouteRecord,
}
複製代碼

假如咱們發生了一個從/parent路由跳轉到/parent/foo路由,首先執行如下代碼生成的route對象post

const route = this.router.match(location, this.current)
複製代碼

所以根據咱們假設的配置,這裏的route裏的matched將會包含/parent/parent/fooRouteRecord。至於具體的match方法代碼就不詳細解釋了。ui

繼續講路由的跳轉,生成route對象後將會執行一個確認的方法confirmTransitionthis

因爲這個方法代碼比較長,咱們拆開來講明,首先看這個方法的入參說明,接受三個參數,route對象在前面已經生成過了,另外兩個是執行完成的回調方法和退出的回調方法。

confirmTransition (route: Route, onComplete: Function, onAbort?: Function)
複製代碼

在代碼的一開始首先判斷當前路由與跳轉的路由是不是同一個路由,若是是則直接退出。

const current = this.current
    const abort = err => {
      if (isError(err)) {
        if (this.errorCbs.length) {
          this.errorCbs.forEach(cb => { cb(err) })
        } else {
          warn(false, 'uncaught error during route navigation:')
          console.error(err)
        }
      }
      onAbort && onAbort(err)
    }
    if (
      isSameRoute(route, current) &&
      // in the case the route map has been dynamically appended to
      route.matched.length === current.matched.length
    ) {
      this.ensureURL()
      return abort()
    }

複製代碼

這裏的ensureURL方法定義在HTML5History的原型鏈上,實際上執行的是保存路由變化歷史記錄,根據pushtruefalse來肯定執行pushState仍是replaceState。這一方法在執行完路由跳轉後一樣會執行一次。

HTML5History.prototype.ensureURL = function ensureURL (push) {
    if (getLocation(this.base) !== this.current.fullPath) {
      var current = cleanPath(this.base + this.current.fullPath);
      push ? pushState(current) : replaceState(current);
    }
  };
複製代碼

繼續看後面的代碼,首先經過resolveQueue對比this.currentroute對象的matched提取三種變化的組件隊列。根據命名咱們直接可得知updateddeactivatedactivated分別對應更新的組件、失效的組件、激活的組件。而後生成一個須要執行方法的隊列queue,根據這個隊列的生成定義,咱們能夠看出執行方法的順序,至於經過extractLeaveGuardsextractUpdateHooks方法提取組件裏的守衛函數就不細說了。

  1. 在失活的組件裏調用離開守衛。

  2. 調用全局的 beforeEach 守衛。

  3. 在重用的組件裏調用 beforeRouteUpdate 守衛

  4. 在激活的路由配置裏調用 beforeEnter。

  5. 解析異步路由組件。

const {
      updated,
      deactivated,
      activated
    } = resolveQueue(this.current.matched, route.matched)

    const queue: Array<?NavigationGuard> = [].concat(
      // in-component leave guards
      extractLeaveGuards(deactivated),
      // global before hooks
      this.router.beforeHooks,
      // in-component update hooks
      extractUpdateHooks(updated),
      // in-config enter guards
      activated.map(m => m.beforeEnter),
      // async components
      resolveAsyncComponents(activated)
    )
複製代碼

看看resolveQueue是如何提取變化的組件。比較currentnext肯定一個變化的位置inext裏的從0i則是updated的部分,i以後的則是activated的部分,而currenti以後的則是deactivated的部分。

function resolveQueue (
  current: Array<RouteRecord>,
  next: Array<RouteRecord>
): {
  updated: Array<RouteRecord>,
  activated: Array<RouteRecord>,
  deactivated: Array<RouteRecord>
} {
  let i
  const max = Math.max(current.length, next.length)
  for (i = 0; i < max; i++) {
    if (current[i] !== next[i]) {
      break
    }
  }
  return {
    updated: next.slice(0, i),
    activated: next.slice(i),
    deactivated: current.slice(i)
  }
}
複製代碼

接下來就是生成迭代器方法iterator,執行runQueue方法。

this.pending = route
    const iterator = (hook: NavigationGuard, next) => {
      if (this.pending !== route) {
        return abort()
      }
      try {
        hook(route, current, (to: any) => {
          if (to === false || isError(to)) {
            // next(false) -> abort navigation, ensure current URL
            this.ensureURL(true)
            abort(to)
          } else if (
            typeof to === 'string' ||
            (typeof to === 'object' && (
              typeof to.path === 'string' ||
              typeof to.name === 'string'
            ))
          ) {
            // next('/') or next({ path: '/' }) -> redirect
            abort()
            if (typeof to === 'object' && to.replace) {
              this.replace(to)
            } else {
              this.push(to)
            }
          } else {
            // confirm transition and pass on the value
            next(to)
          }
        })
      } catch (e) {
        abort(e)
      }
    }
    
    runQueue(queue, iterator, () => {
      const postEnterCbs = []
      const isValid = () => this.current === route
      // wait until async components are resolved before
      // extracting in-component enter guards
      const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)
      const queue = enterGuards.concat(this.router.resolveHooks)
      runQueue(queue, iterator, () => {
        if (this.pending !== route) {
          return abort()
        }
        this.pending = null
        onComplete(route)
        if (this.router.app) {
          this.router.app.$nextTick(() => {
            postEnterCbs.forEach(cb => { cb() })
          })
        }
      })
    })
複製代碼

runQueue方法的代碼並不複雜,一個遞歸執行隊列的方法,使用iterator(fn參數)執行queueiterator裏給hook傳入的參數分別表明tofromnext,在隊列執行完後執行傳入的回調方法。這裏執行過程表明了vue-router的守衛函數的執行函數。

export function runQueue (queue: Array<?NavigationGuard>, fn: Function, cb: Function) {
  const step = index => {
    if (index >= queue.length) {
      cb()
    } else {
      if (queue[index]) {
        fn(queue[index], () => {
          step(index + 1)
        })
      } else {
        step(index + 1)
      }
    }
  }
  step(0)
}
複製代碼

綜上所述,路由跳轉的變化大概上已經解釋完,固然這並非完整的執行邏輯,只是路由跳轉大概的過程差很少就是如此。

相關文章
相關標籤/搜索