Read vue-Router source code

Vue.use

When using routes in a VUE project, you need to register routes through vue.use (VueRouter).

In vue/ SRC /core/index.js, the initGlobalAPI(vue) method is called. In vue/ SRC /core/global-api/index.js, the initGlobalAPI method is defined and exported. InitUse (Vue) is called in the initGlobalAPI method. In Vue/SRC /core/global-api/use.js, find initUse, where vue.use is defined.

export function initUse (Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | Object) {
    // Get the list of registered plug-ins
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    // Avoid duplicate registration
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }

    // Get other parameters
    const args = toArray(arguments.1)
    // Push the current vue instance to the parameter array head
    args.unshift(this)
    Call Install when the component has an Install method, or when the component itself is a callable method, to complete the registration action
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    // Add the registered component to the registered array
    installedPlugins.push(plugin)
    
    return this}}Copy the code

In vue/ SRC /shared/util.js, find the toArray method, which converts an array-like object into an array.

export function toArray (list: any, start? : number) :Array<any> {
  start = start || 0
  let i = list.length - start
  const ret: Array<any> = new Array(i)
  while (i--) {
    ret[i] = list[i + start]
  }
  return ret
}
Copy the code

install

The install method is exported in vue-router/ SRC /install.js.

export function install (Vue) {
  // Install can only be called once
  if (install.installed && _Vue === Vue) return
  install.installed = true

  _Vue = Vue

  const isDef = v= >v ! = =undefined

  const registerInstance = (vm, callVal) = > {
    / / call the vm $options. _parentVnode. Data. RegisterRouteInstance (vm, callVal)
    let i = vm.$options._parentVnode
    if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
      i(vm, callVal)
    }
  }

  // Add a hook function to each component
  Vue.mixin({
    beforeCreate () {
      // Check whether it is the root component. Only the root component has the Router object
      if (isDef(this.$options.router)) {
        // Set the root route
        this._routerRoot = this
        // Set the Router object
        this._router = this.$options.router
        // Initialize the route
        this._router.init(this)
        // Define the _route attribute to point to the current route object
        Vue.util.defineReactive(this.'_route'.this._router.history.current)
      } else {
        // Non-root component, layers pointing to the root route
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
      }
      registerInstance(this.this)
    },
    destroyed () {
      registerInstance(this)}})// Define $router on the Vue prototype object
  Object.defineProperty(Vue.prototype, '$router', {
    get () { return this._routerRoot._router }
  })
  // Define $route on the Vue prototype object
  Object.defineProperty(Vue.prototype, '$route', {
    get () { return this._routerRoot._route }
  })

  // Globally register components
  Vue.component('RouterView', View)
  Vue.component('RouterLink', Link)

  // use the same hook merging strategy for route hooks
  conststrats = Vue.config.optionMergeStrategies strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate =  strats.created }Copy the code

Note: The defineReactive method was analyzed in the vUE responsive principle of source code analysis (see this if you can’t open it).

The VueRouter constructor

After registering the route in your project, the next step is to instantiate the route.

var router = new VueRouter({
  mode: 'hash'.routes: [{path: '/'.component: Home },
    { path: '/foo'.component: Foo },
    { path: '/bar'.component: Bar }
  ]
})
Copy the code

In vue-router/ SRC /index.js, take a look at what the constructor does.

export default class VueRouter {
  static install: () = > void
  static version: string
  static isNavigationFailure: Function
  static NavigationFailureType: any
  static START_LOCATION: Route

  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)

    // Set the routing mode, which is hash by default
    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}`)}}}/ /... A variety of methods
}
Copy the code

The constructor mainly calls the createMatcher method and sets mode to generate the corresponding history object.

createMatcher

In vue-router source code vue-router/ SRC /create-matcher.js

export function createMatcher (
  routes: Array<RouteConfig>,
  router: VueRouter
) :Matcher {
  const { pathList, pathMap, nameMap } = createRouteMap(routes)

  function addRoutes (routes) {
    // 用到了 pathList, pathMap, nameMap 
    // ...
  }

  function addRoute (parentOrRoute, route) {
    // 用到了 pathList, pathMap, nameMap 
    // ...
  }

  function getRoutes () {
    // 用到了 pathList, pathMap
    // ...
  }

  function match (raw: RawLocation, currentRoute? : Route, redirectedFrom? : Location) :Route {
    // 用到了 pathList, pathMap, nameMap 
    // ...
  }

  / /... Some other internal methods

  return {
    match,
    addRoute,
    getRoutes,
    addRoutes
  }
}
Copy the code

CreateMatcher returns a Matcher object with the match, addRoute, getRoutes, and addRoutes methods. Return an object with three methods: pathList, pathMap, and nameMap. Save the three methods in a closure so that Matcher objects can use them.

createRouteMap

In vue-router source code vue-router/ SRC /create-route-map.js

export function createRouteMap (
  routes: Array<RouteConfig>, oldPathList? :Array<string>, oldPathMap? : Dictionary<RouteRecord>, oldNameMap? : Dictionary<RouteRecord>, parentRoute? : RouteRecord) :{
  pathList: Array<string>,
  pathMap: Dictionary<RouteRecord>,
  nameMap: Dictionary<RouteRecord>
} {
  // Create a mapping table
  // pathList is used to control the route matching priority
  const pathList: Array<string> = oldPathList || []
  const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null)
  const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null)

  // Add routing records to the mapping table
  routes.forEach(route= > {
    addRouteRecord(pathList, pathMap, nameMap, route, parentRoute)
  })

  // Make sure the wildcard "*" is always at the end of the path list
  for (let i = 0, l = pathList.length; i < l; i++) {
    if (pathList[i] === The '*') {
      pathList.push(pathList.splice(i, 1) [0])
      l--
      i--
    }
  }

  // In the development environment, if the first character of the route path is not '/', a warning is reported
  if (process.env.NODE_ENV === 'development') {
    const found = pathList
      .filter(path= > path && path.charAt(0)! = =The '*' && path.charAt(0)! = ='/')
    if (found.length > 0) {
      const pathNames = found.map(path= > ` -${path}`).join('\n')
      warn(false.`Non-nested routes must include a leading slash character. Fix the following routes: \n${pathNames}`)}}return {
    pathList,
    pathMap,
    nameMap
  }
}
Copy the code

init

beforeCreate () {
    // Check whether it is the root component. Only the root component has the Router object
    if (isDef(this.$options.router)) {
        // Set the root route
        this._routerRoot = this
        // Set the Router object
        this._router = this.$options.router
        // Initialize the route
        this._router.init(this)
        // Define the _route attribute to point to the current route object
        Vue.util.defineReactive(this.'_route'.this._router.history.current)
    } else {
        // Non-root component, which points to the root route of the parent component and eventually to the root component
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
    }
    registerInstance(this.this)}Copy the code

The route is initialized when the component calls the beforeCreate hook, which is VueRouter’s init method.

init (app: any /* Vue component instance */) {
    // In non-production environments, registered routing reports are reportedprocess.env.NODE_ENV ! = ='production' &&
      assert(
        install.installed,
        `not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
          `before creating root instance.`
      )

    // Save the component instance
    this.apps.push(app)

    // When a component is destroyed, the component instance is removed from apps
    app.$once('hook:destroyed'.() = > {
      const index = this.apps.indexOf(app)
      if (index > -1) this.apps.splice(index, 1)
      // When the root component is destroyed, this.app points back to the first component instance in the Apps array
      if (this.app === app) this.app = this.apps[0] | |null
      // Resets history when the apps array has no component instance
      if (!this.app) this.history.teardown()
    })

    // If the root component is set, return
    if (this.app) {
      return
    }

    this.app = app

    const history = this.history

    if (history instanceof HTML5History || history instanceof HashHistory) {
      const handleInitialScroll = routeOrError= > {
        const from = history.current
        const expectScroll = this.options.scrollBehavior
        const supportsScroll = supportsPushState && expectScroll

        if (supportsScroll && 'fullPath' in routeOrError) {
          handleScroll(this, routeOrError, from.false)}}SetupListeners is used to register the actual listeners for popState and Hashchange
      const setupListeners = routeOrError= > {
        history.setupListeners()
        handleInitialScroll(routeOrError)
      }
      // Route jump
      history.transitionTo(
        history.getCurrentLocation(),
        setupListeners,
        setupListeners
      )
    }
    // listen is used to mount the this.cb callback, which sets an array of apps variables to _route for each component instance and triggers dep.notify().
    history.listen(route= > {
      this.apps.forEach(app= > {
        app._route = route
      })
    })
}
Copy the code

From the model: the history getCurrentLocation () returns the getHash ()

In vue – vue router source code – the router/SRC/history/hash in js

function getHash () :string {
  // We can't use window.location.hash here because it's not
  // consistent across browsers - Firefox will pre-decode it!
  let href = window.location.href
  const index = href.indexOf(The '#')
  // empty path
  if (index < 0) return ' '

  href = href.slice(index + 1)

  return href
}
Copy the code

History pattern: history. GetCurrentLocation () returns the getLocation (enclosing base)

In vue – vue router source code – the router/SRC/history/HTML 5. Js

function getLocation (base: string) :string {
  let path = window.location.pathname
  if (base && path.toLowerCase().indexOf(base.toLowerCase()) === 0) {
    path = path.slice(base.length)
  }
  return (path || '/') + window.location.search + window.location.hash
}
Copy the code

transitionTo

In vue – vue router source code – the router/SRC/history/base. Js

transitionTo ( location: RawLocation, onComplete? :Function, onAbort? :Function
  ) {
    let route
    try {
      // Match the new route
      route = this.router.match(location, this.current)
    } catch (e) {
      this.errorCbs.forEach(cb= > {
        cb(e)
      })
      throw e
    }
    // Assign the original route to prev
    const prev = this.current
    // Call confirmTransition to confirm the jump
    this.confirmTransition(
      route,
      // Successful callback
      () = > {
        // The updateRoute method points this.current to the new route, where this.cb (the callback passed in listen) is executed.
        this.updateRoute(route)
        / / setupListeners execution
        onComplete && onComplete(route)
        / / update the url
        this.ensureURL()
        // Iterate over to call afterHooks
        this.router.afterHooks.forEach(hook= > {
          hook && hook(route, prev)
        })
        // this.ready defaults to false, meaning that the readyCbs callback is executed only once
        if (!this.ready) {
          this.ready = true
          this.readyCbs.forEach(cb= > {
            cb(route)
          })
        }
      },
      // Error handling
      err= > {
        if (onAbort) {
          onAbort(err)
        }
        if (err && !this.ready) {
          if(! isNavigationFailure(err, NavigationFailureType.redirected) || prev ! == START) {this.ready = true
            this.readyErrorCbs.forEach(cb= > {
              cb(err)
            })
          }
        }
      }
    )
}
Copy the code

match

Let’s go back to the createMatcher method and see how match matches routes. Okay

function match (raw: RawLocation, currentRoute? : Route, redirectedFrom? : Location) :Route {
    /** normalizeLocation serializes a Location object * {* _normalized: true, * PATH, * query, * hash *} */
    const location = normalizeLocation(raw, currentRoute, false, router)

    const { name } = location
    // Name the route
    if (name) {
      // Match routing records from nameMap mapping table
      const record = nameMap[name]
      // No match result is reported in non-production environment
      if(process.env.NODE_ENV ! = ='production') {
        warn(record, `Route with name '${name}' does not exist`)}// There is no matching result
      if(! record)return _createRoute(null, location)
      
      // List of production parameters
      const paramNames = record.regex.keys
        .filter(key= >! key.optional) .map(key= > key.name)
      // Initialize location.params
      if (typeoflocation.params ! = ='object') {
        location.params = {}
      }
      // Add missing parameters in location
      if (currentRoute && typeof currentRoute.params === 'object') {
        for (const key in currentRoute.params) {
          if(! (keyin location.params) && paramNames.indexOf(key) > -1) {
            location.params[key] = currentRoute.params[key]
          }
        }
      }
      // Check parameters
      location.path = fillParams(record.path, location.params, `named route "${name}"`)
      return _createRoute(record, location, redirectedFrom)
    } 
    // Non-named route
    else if (location.path) {
      location.params = {}
      for (let i = 0; i < pathList.length; i++) {
        // Use pathList and pathMap to find routing records
        const path = pathList[i]
        const record = pathMap[path]
        if (matchRoute(record.regex, location.path, location.params)) {
          return _createRoute(record, location, redirectedFrom)
        }
      }
    }
    // No routing record was matched in the mapping table
    return _createRoute(null, location)
}
Copy the code

_createRoute and createRoute

function _createRoute (record: ? RouteRecord, location: Location, redirectedFrom? : Location) :Route {
    / / redirection
    if (record && record.redirect) {
      return redirect(record, redirectedFrom || location)
    }
    / / alias
    if (record && record.matchAs) {
      return alias(record, location, record.matchAs)
    }
    return createRoute(record, location, redirectedFrom, router)
  }

  return {
    match,
    addRoute,
    getRoutes,
    addRoutes
  }
}
Copy the code

CreateRoute is in vue-router source code vue-router/ SRC /util/route.js

export function createRoute (record: ? RouteRecord, location: Location, redirectedFrom? :? Location, router? : VueRouter) :Route {
  const stringifyQuery = router && router.options.stringifyQuery
  // Clone the query parameter
  let query: any = location.query || {}
  try {
    query = clone(query)
  } catch (e) {}
  // Create a routing object
  const route: Route = {
    name: location.name || (record && record.name),
    meta: (record && record.meta) || {},
    path: location.path || '/'.hash: location.hash || ' ',
    query,
    params: location.params || {},
    fullPath: getFullPath(location, stringifyQuery),
    matched: record ? formatMatch(record) : []
  }
  if (redirectedFrom) {
    route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery)
  }
  // Freeze the route object to make it unmodifiable
  return Object.freeze(route)
}
// Get the full path
function getFullPath (
  { path, query = {}, hash = ' ' },
  _stringifyQuery
) :string {
  const stringify = _stringifyQuery || stringifyQuery
  return (path || '/') + stringify(query) + hash
}
// Get a list of all nested routing records containing the current route
function formatMatch (record: ? RouteRecord) :Array<RouteRecord> {
  const res = []
  while (record) {
    res.unshift(record)
    record = record.parent
  }
  return res
}
Copy the code

confirmTransition

Go back to the transitionTo route jump method and look at confirmTransition to confirm the jump.

// Used to compare routing records
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++) {
    // abort the loop at the same time
    if(current[i] ! == next[i]) {break}}return {
    updated: next.slice(0, i), / / reusable
    activated: next.slice(i), // It needs to be rerendered
    deactivated: current.slice(i) / / the deactivation
  }
}
confirmTransition (route: Route, onComplete: Function, onAbort? :Function) {
    // Get the current route
    const current = this.current
    // Save the route
    this.pending = route
    
    / / stop
    const abort = err= > {
      if(! isNavigationFailure(err) && 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)
    }

    const lastRouteIndex = route.matched.length - 1
    const lastCurrentIndex = current.matched.length - 1
    // The same route does not jump
    if (
      isSameRoute(route, current) &&
      lastRouteIndex === lastCurrentIndex &&
      route.matched[lastRouteIndex] === current.matched[lastCurrentIndex]
    ) {
      // Update the URL with replace
      this.ensureURL()
      return abort(createNavigationDuplicatedError(current, route))
    }

    // Find reusable components, inactivated components, and rendered components by comparing the routing nested by the record list
    const { updated, deactivated, activated } = resolveQueue(
      this.current.matched,
      route.matched
    )

    // Route guard array
    const queue: Array<? NavigationGuard> = [].concat(// Deactivate the component hook
      extractLeaveGuards(deactivated),
      // global beforeEach hook
      this.router.beforeHooks,
      // Reuse component hooks
      extractUpdateHooks(updated),
      // Executes the beforeEnter hook for the component that needs to be rerendered
      activated.map(m= > m.beforeEnter),
      // Parse the asynchronous routing component
      resolveAsyncComponents(activated)
    )

    // Iterator to execute the route guard hook
    const iterator = (hook: NavigationGuard, next) = > {
      // The route is not equal, abort the jump
      if (this.pending ! == route) {return abort(createNavigationCancelledError(current, route))
      }
      try {
        // Execute the hook
        hook(route, current, (to: any) = > {
          // To is the argument when next() is called in the route guard in the project
          // The client executes next(false) to abort the jump and update the URL
          if (to === false) {
            this.ensureURL(true)
            abort(createNavigationAbortedError(current, route))
          } 
          // Next takes the Error instance to abort the jump and update the URL
          else if (isError(to)) {
            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(createNavigationRedirectedError(current, route))
            if (typeof to === 'object' && to.replace) {
              this.replace(to)
            } else {
              this.push(to)
            }
          } else {
            // Execute step(index + 1), which calls the next hook in queue
            next(to)
          }
        })
      } catch (e) {
        abort(e)
      }
    }

    runQueue(queue, iterator, () = > {
      // This is the callback to queue after each step
      const enterGuards = extractEnterGuards(activated)
      const queue = enterGuards.concat(this.router.resolveHooks)
      // Next execute the route guard hook for the component that needs to be rerendered
      runQueue(queue, iterator, () = > {
        // The route guard hook of the component that needs to be rerendered completes the callback
        if (this.pending ! == route) {return abort(createNavigationCancelledError(current, route))
        }
        / / reset pending
        this.pending = null
        // Perform the jump to complete the callback
        onComplete(route)
        if (this.router.app) {
          this.router.app.$nextTick(() = > {
            handleRouteEntered(route)
          })
        }
      })
    })
}
Copy the code

In vue-router source vue-router/ SRC /util/async.js

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)}Copy the code

Queue Array of route guards

  1. extractLeaveGuards(deactivated)Deactivate component hooks
function extractLeaveGuards (deactivated: Array<RouteRecord>) :Array<?Function> {
  return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true)}Copy the code
function extractGuards (
  records: Array<RouteRecord>,
  name: string,
  bind: Function, reverse? : boolean) :Array<?Function> {
  const guards = flatMapComponents(records, (def, instance, match, key) = > {
    // Get the hook array
    const guard = extractGuard(def, name)
    / / bind this
    if (guard) {
      return Array.isArray(guard)
        ? guard.map(guard= > bind(guard, instance, match, key))
        : bind(guard, instance, match, key)
    }
  })
  // Reverse reverses the hook array as true, indicating that this type of hook is executed from the child component
  return flatten(reverse ? guards.reverse() : guards)
}
// Bind this to each hook function to point to the component instance
function bindGuard (guard: NavigationGuard, instance: ? _Vue): ?NavigationGuard {
  if (instance) {
    return function boundRouteGuard () {
      return guard.apply(instance, arguments)}}}// Returns the array of hooks specified by the component
function extractGuard (
  def: Object | Function,
  key: string
) :NavigationGuard | Array<NavigationGuard> {
  if (typeofdef ! = ='function') {
    // extend now so that global mixins are applied.
    def = _Vue.extend(def)
  }
  return def.options[key]
}
Copy the code

In vue-router source code vue-router/ SRC /util/resolve-components

export function flatMapComponents (
  matched: Array<RouteRecord>,
  fn: Function
) :Array<?Function> {
  return flatten(matched.map(m= > {
    // Return parameters related to each component to the callback
    return Object.keys(m.components).map(key= > fn(
      m.components[key],
      m.instances[key],
      m, 
      key
    ))
  }))
}
// Array dimension reduction
export function flatten (arr: Array<any>) :Array<any> {
  return Array.prototype.concat.apply([], arr)
}
Copy the code
  1. this.router.beforeHooksGlobal beforeEach hook

BeforeEach is registered in the VueRouter method

class VueRouter {
  beforeEach (fn: Function) :Function {
    return registerHook(this.beforeHooks, fn)
  }
}
function registerHook (list: Array<any>, fn: Function) :Function {
  list.push(fn)
  return () = > {
    const i = list.indexOf(fn)
    if (i > -1) list.splice(i, 1)}}Copy the code
  1. extractUpdateHooks(updated)Reusable component hooks
function extractUpdateHooks (updated: Array<RouteRecord>) :Array<?Function> {
  return extractGuards(updated, 'beforeRouteUpdate', bindGuard)
}
Copy the code
  1. Execute the beforeEnter hook of the component that needs to be rerendered. Activated. Map (m => m. befioreEnter

  2. ResolveAsyncComponents (Activated) Resolves asynchronous routing components

{component:resolve => require([‘@/views/user/index’], resolve)}

In vue-router source code vue-router/ SRC /util/resolve-components

export function resolveAsyncComponents (matched: Array<RouteRecord>) :Function {
  return (to, from, next) = > {
    let hasAsync = false
    let pending = 0
    let error = null

    flatMapComponents(matched, (def, _, match, key) = > {
      If def is a function and there is no CID, we assume it is an asynchronous component
      if (typeof def === 'function' && def.cid === undefined) {
        hasAsync = true
        pending++

        // Successful callback
        const resolve = once(resolvedDef= > {
          if (isESModule(resolvedDef)) {
            resolvedDef = resolvedDef.default
          }
          // If the constructor is passed in, create a component with vue.extend
          def.resolved = typeof resolvedDef === 'function'
            ? resolvedDef
            : _Vue.extend(resolvedDef)
          // Place the component in the routing record
          match.components[key] = resolvedDef
          // Wait count -1
          pending--
          // All parses are completed, then the next step
          if (pending <= 0) {
            next()
          }
        })

        // Failed callback
        const reject = once(reason= > {
          const msg = `Failed to resolve async component ${key}: ${reason}`process.env.NODE_ENV ! = ='production' && warn(false, msg)
          if(! error) { error = isError(reason) ? reason :new Error(msg)
            next(error)
          }
        })

        let res
        try {
          // Execute asynchronous component functions
          res = def(resolve, reject)
        } catch (e) {
          reject(e)
        }
        if (res) {
          // Perform the callback
          if (typeof res.then === 'function') {
            res.then(resolve, reject)
          } else {
            // new syntax in Vue 2.3
            const comp = res.component
            if (comp && typeof comp.then === 'function') {
              comp.then(resolve, reject)
            }
          }
        }
      }
    })
    // Not the next direct step for asynchronous components
    if(! hasAsync) next() } }function once (fn) {
  let called = false
  return function (. args) {
    if (called) return
    called = true
    return fn.apply(this, args)
  }
}
Copy the code
  1. The first runQueue callback is executed after the hooks 1-5 are completed
runQueue(queue, iterator, () = > {
    // This is the callback to queue after each step
    const enterGuards = extractEnterGuards(activated)
    const queue = enterGuards.concat(this.router.resolveHooks)
    // Next execute the route guard hook for the component that needs to be rerendered
    runQueue(queue, iterator, () = > {
        // The route guard hook of the component that needs to be rerendered completes the callback
        if (this.pending ! == route) {return abort(createNavigationCancelledError(current, route))
        }
        / / reset pending
        this.pending = null
        // Perform the jump to complete the callback
        onComplete(route)
        if (this.router.app) {
            this.router.app.$nextTick(() = > {
                handleRouteEntered(route)
            })
        }
    })
})
Copy the code
function extractEnterGuards (
  activated: Array<RouteRecord>
) :Array<?Function> {
  return extractGuards(
    activated,
    'beforeRouteEnter'.(guard, _, match, key) = > {
      return bindEnterGuard(guard, match, key)
    }
  )
}
function bindEnterGuard (guard: NavigationGuard, match: RouteRecord, key: string) :NavigationGuard {
  return function routeEnterGuard (to, from, next) {
    return guard(to, from.cb= > {
      // cb is the function that pushes the route record on enteredCbs
      if (typeof cb === 'function') {
        if(! match.enteredCbs[key]) { match.enteredCbs[key] = [] } match.enteredCbs[key].push(cb) } next(cb) }) } }Copy the code

Const enterGuards = extractEnterGuards(activated) Returns the beforeRouteEnter hook.

Const queue = enterGuards. Concat (this.router. ResolveHooks) Splices resolveHooks.

RunQueue Starts executing a new queue.

  1. Perform beforeRouteEnter

The beforeRouteEnter guard cannot access this because the guard is called before navigation confirmation, so the upcoming new component has not yet been created.

However, you can access the component instance by passing a callback to Next. The callback is executed when the navigation is validated, and the component instance is taken as an argument to the callback method.

beforeRouteEnter (to, from, next) {
  next(vm= > {
    // Access component instances through 'VM'})}Copy the code

Note that beforeRouteEnter is the only guard that supports passing a callback to Next. For beforeRouteUpdate and beforeRouteLeave, this is already available, so passing callbacks are not supported because they are not necessary.

  1. Perform resolveHooks

  2. 7-8 Execute the second runQueue callback after completion,

runQueue(queue, iterator, () = > {
    // The route guard hook of the component that needs to be rerendered completes the callback
    if (this.pending ! == route) {return abort(createNavigationCancelledError(current, route))
    }
    / / reset pending
    this.pending = null
    // Perform the jump to complete the callback
    onComplete(route)
    if (this.router.app) {
        this.router.app.$nextTick(() = > {
            handleRouteEntered(route) // The callback in enteredCbs is executed here})}})Copy the code

Vue-router/SRC /util/route.js

export function handleRouteEntered (route: Route) {
  for (let i = 0; i < route.matched.length; i++) {
    const record = route.matched[i]
    for (const name in record.instances) {
      const instance = record.instances[name]
      const cbs = record.enteredCbs[name]
      if(! instance || ! cbs)continue
      delete record.enteredCbs[name]
      for (let i = 0; i < cbs.length; i++) {
        if(! instance._isBeingDestroyed) cbs[i](instance) } } } }Copy the code

Complete navigation parsing process

  1. Navigation is triggered.
  2. Call the beforeRouteLeave guard in the deactivated component.
  3. Call the global beforeEach guard.
  4. Call the beforeRouteUpdate guard (2.2+) in the reused component.
  5. Call beforeEnter in routing configuration.
  6. Parse the asynchronous routing component.
  7. Call beforeRouteEnter in the activated component.
  8. Call the global beforeResolve guard (2.5+).
  9. Navigation confirmed.
  10. Call the global afterEach hook.
  11. Trigger a DOM update.
  12. Call the callback passed to Next in the beforeRouteEnter guard, and the created component instance is passed in as an argument to the callback.

reference

VueRouter source depth analysis

Navigation guard