Routing principle

Before parsing the source code, first to understand the implementation principle of front-end routing. Front-end routing implementation is actually very simple, the essence is to monitor URL changes, and then match routing rules, display the corresponding page, and do not need to refresh. Currently there are only two ways to implement routing for a single page

  • Hash pattern
  • The history mode

www.test.com/#/ is a Hash URL. When the Hash value after # changes, no data is requested from the server. The hashchange event is used to listen for changes in the URL and jump to the page.

History mode is a new feature of HTML5 that is much nicer than Hash urls

VueRouter source code analysis

Mind maps for important functions

The following mind map lists some of the important functions in the source code

Routing registered

Before you start, clone a copy of the source code and review it. Because of the length, there are many jumps between functions.

Before using the route, you need to call vue.use (VueRouter) because the plug-in can use Vue

export function initUse (Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | Object) {
    // Check for repeated plug-in installation
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    if (installedPlugins.indexOf(plugin) > - 1) {
      return this
    }
    const args = toArray(arguments.1)
    / / insert the Vue
    args.unshift(this)
    // Most plug-ins have an install function
    // Use this function to make Vue available to the plug-in
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this}}Copy the code

Let’s look at a partial implementation of the install function

export function install (Vue) {
  // Make sure install calls once
  if (install.installed && _Vue === Vue) return
  install.installed = true
  // Assign Vue to the global variable
  _Vue = Vue
  const registerInstance = (vm, callVal) = > {
    let i = vm.$options._parentVnode
    if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
      i(vm, callVal)
    }
  }
  // Mix in the implementation of each component's hook function
  // Can be found when the 'beforeCreate' hook is executed
  // Initializes the route
  Vue.mixin({
    beforeCreate () {
      // Determine whether the component has a Router object, which is only available on the root component
      if (isDef(this.$options.router)) {
        // Set the root route to itself
        this._routerRoot = this
        this._router = this.$options.router
        // Initialize the route
        this._router.init(this)
        // Very important, implement bidirectional binding for the _route attribute
        // Trigger component rendering
        Vue.util.defineReactive(this.'_route'.this._router.history.current)
      } else {
        // Used for router-view hierarchy judgment
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
      }
      registerInstance(this.this)
    },
    destroyed () {
      registerInstance(this)}})// Globally register components router-link and router-view
  Vue.component('RouterView', View)
  Vue.component('RouterLink', Link)
}
Copy the code

For route registration, the core is to call vue.use (VueRouter) so that VueRouter can use Vue. VueRouter’s install function is then called via Vue. In this function, the core is to mix the component with the hook function and global registration of two routing components.

VueRouter instantiation

After installing the plug-in, instantiate the VueRouter.

const Home = { template: '<div>home</div>' }
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }

// 3. Create the router
const router = new VueRouter({
  mode: 'hash'.base: __dirname,
  routes: [{path: '/'.component: Home }, // all paths are defined without the hash.
    { path: '/foo'.component: Foo },
    { path: '/bar'.component: Bar }
  ]
})
Copy the code

Take a look at VueRouter’s constructor

constructor(options: RouterOptions = {}) {
    // ...
    // Route matching object
    this.matcher = createMatcher(options.routes || [], this)

    // Take different routing modes according to 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}`)}}}Copy the code

In the process of instantiating the VueRouter, the core is to create a routing match object and take different routing modes based on mode.

Create a route matching object

export function createMatcher (routes: Array
       
        , router: VueRouter
       ) :Matcher {
    // Create a routing mapping table
  const { pathList, pathMap, nameMap } = createRouteMap(routes)
    
  function addRoutes (routes) {
    createRouteMap(routes, pathList, pathMap, nameMap)
  }
  // Route match
  function match (raw: RawLocation, currentRoute? : Route, redirectedFrom? : Location) :Route {
    / /...
  }

  return {
    match,
    addRoutes
  }
}
Copy the code

The createMatcher function creates a routing mapping table, then uses closures to allow addRoutes and match to use several objects from the routing mapping table, and finally returns a Matcher object.

How to create a mapping table in createMatcher

export function createRouteMap (routes: Array
       
        , oldPathList? : Array
        
         , oldPathMap? : Dictionary
         
          , oldNameMap? : Dictionary
          
         
        
       ) :{
  pathList: Array<string>;
  pathMap: Dictionary<RouteRecord>;
  nameMap: Dictionary<RouteRecord>;
} {
  // Create a mapping table
  const pathList: Array<string> = oldPathList || []
  const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null)
  const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null)
  // Walk through the route configuration and add a route record for each configuration
  routes.forEach(route= > {
    addRouteRecord(pathList, pathMap, nameMap, route)
  })
  // Make sure the wildcard is at the end
  for (let i = 0, l = pathList.length; i < l; i++) {
    if (pathList[i] === The '*') {
      pathList.push(pathList.splice(i, 1) [0])
      l--
      i--
    }
  }
  return {
    pathList,
    pathMap,
    nameMap
  }
}
// Add a route record
function addRouteRecord (pathList: Array
       
        , pathMap: Dictionary
        
         , nameMap: Dictionary
         
          , route: RouteConfig, parent? : RouteRecord, matchAs? : string
         
        
       ) {
  // Get the attributes of the route configuration
  const { path, name } = route
  const pathToRegexpOptions: PathToRegexpOptions = route.pathToRegexpOptions || {}
  // Format the URL, replace /
  const normalizedPath = normalizePath(
    path,
    parent,
    pathToRegexpOptions.strict
  )
  // Generate a record object
  const record: RouteRecord = {
    path: normalizedPath,
    regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
    components: route.components || { default: route.component },
    instances: {},
    name,
    parent,
    matchAs,
    redirect: route.redirect,
    beforeEnter: route.beforeEnter,
    meta: route.meta || {},
    props: route.props == null
      ? {}
      : route.components
        ? route.props
        : { default: route.props }
  }

  if (route.children) {
    // Add routing records to the children attribute of the recursive route configuration
    route.children.forEach(child= > {
      const childMatchAs = matchAs
        ? cleanPath(`${matchAs}/${child.path}`)
        : undefined
      addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)
    })
  }
  // If the route has an alias
  // Add a routing record to the alias
  if(route.alias ! = =undefined) {
    const aliases = Array.isArray(route.alias)
      ? route.alias
      : [route.alias]

    aliases.forEach(alias= > {
      const aliasRoute = {
        path: alias,
        children: route.children
      }
      addRouteRecord(
        pathList,
        pathMap,
        nameMap,
        aliasRoute,
        parent,
        record.path || '/' // matchAs)})}// Update the mapping table
  if(! pathMap[record.path]) { pathList.push(record.path) pathMap[record.path] = record }// Add a record to the named route
  if (name) {
    if(! nameMap[name]) { nameMap[name] = record }else if(process.env.NODE_ENV ! = ='production' && !matchAs) {
      warn(
        false.`Duplicate named routes definition: ` +
        `{ name: "${name}", path: "${record.path}" }`)}}}Copy the code

This section describes the process of creating a route matching object. You can create a route mapping table based on user-defined routing rules.

Route initialization

When the root component calls the beforeCreate hook function, the following code is executed

beforeCreate () {
// Only the root component has the Router attribute, so the root component initializes the route when it initializes
  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)}Copy the code

Now what does route initialization do

init(app: any /* Vue component instance */) {
    // Save the component instance
    this.apps.push(app)
    // Return if the root component already exists
    if (this.app) {
      return
    }
    this.app = app
    // Assign the routing mode
    const history = this.history
    // Determine the routing mode, using hash mode as an example
    if (history instanceof HTML5History) {
      history.transitionTo(history.getCurrentLocation())
    } else if (history instanceof HashHistory) {
      // Add hashchange listener
      const setupHashListener = (a)= > {
        history.setupListeners()
      }
      // Route jump
      history.transitionTo(
        history.getCurrentLocation(),
        setupHashListener,
        setupHashListener
      )
    }
    // This callback is called in transitionTo
    // Assign the component's _route attribute to trigger component rendering
    history.listen(route= > {
      this.apps.forEach(app= > {
        app._route = route
      })
    })
  }
Copy the code

During route initialization, the core is to jump the route, change the URL, and render the corresponding component. Let’s look at how the route jumps.

Routing hop

transitionTo (location: RawLocation, onComplete? :Function, onAbort? :Function) {
  // Obtain the matched routing information
  const route = this.router.match(location, this.current)
  // Confirm route switchover
  this.confirmTransition(route, () => {
    // The following is a successful or failed route switchover callback
    // Update the routing information and assign the component's _route attribute to trigger component rendering
    // Call the afterHooks function
    this.updateRoute(route)
    // Add hashchange listener
    onComplete && onComplete(route)
    / / update the URL
    this.ensureURL()
    // Execute the ready callback only once
    if (!this.ready) {
      this.ready = true
      this.readyCbs.forEach(cb= > { cb(route) })
    }
  }, err => {
  // Error handling
    if (onAbort) {
      onAbort(err)
    }
    if (err && !this.ready) {
      this.ready = true
      this.readyErrorCbs.forEach(cb= > { cb(err) })
    }
  })
}
Copy the code

Before redirecting a route, you need to obtain the matched route information. Therefore, how to obtain the matched route information first

function match (raw: RawLocation, currentRoute? : Route, redirectedFrom? : Location) :Route {
  // serialize the URL
  // For this url, for example/ABC? foo=bar&baz=qux#hello
  // Will serialize to/ABC
  // Hash #hello
  // The parameters are foo: 'bar', baz: 'qux'
  const location = normalizeLocation(raw, currentRoute, false, router)
  const { name } = location
  // If it is a named route, check whether the named route is configured in the record
  if (name) {
    const record = nameMap[name]
    // No matching route was found
    if(! record)return _createRoute(null, location)
    const paramNames = record.regex.keys
      .filter(key= >! key.optional) .map(key= > key.name)
    // Parameter processing
    if (typeoflocation.params ! = ='object') {
      location.params = {}
    }
    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]
        }
      }
    }
    if (record) {
      location.path = fillParams(record.path, location.params, `named route "${name}"`)
      return _createRoute(record, location, redirectedFrom)
    }
  } else if (location.path) {
    // Non-named route processing
    location.params = {}
    for (let i = 0; i < pathList.length; i++) {
     // Find the record
      const path = pathList[i]
      const record = pathMap[path]
      // If the route matches, the route is created
      if (matchRoute(record.regex, location.path, location.params)) {
        return _createRoute(record, location, redirectedFrom)
      }
    }
  }
  // There is no matching route
  return _createRoute(null, location)
}
Copy the code

Now how do you create a route

// Create different routes according to the condition
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, router)
}

export function createRoute (record: ? RouteRecord, location: Location, redirectedFrom? :? Location, router? : VueRouter) :Route {
  const stringifyQuery = router && router.options.stringifyQuery
  // Clone parameters
  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)
  }
  // The yield object cannot be modified
  return Object.freeze(route)
}
// Get the routing record containing all nested path fragments of the current route
// Contains matching records from the root route to the current route, from top to bottom
function formatMatch(record: ? RouteRecord) :Array<RouteRecord> {
  const res = []
  while (record) {
    res.unshift(record)
    record = record.parent
  }
  return res
}
Copy the code

Now that the matching route is complete, go back to the transitionTo function and execute confirmTransition

transitionTo (location: RawLocation, onComplete? :Function, onAbort? :Function) {
  // Confirm route switchover
  this.confirmTransition(route, () => {}
}
confirmTransition(route: Route, onComplete: Function, onAbort? :Function) {
  const current = this.current
  // Interrupts the redirect routing function
  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)
  }
  // Do not jump to the same route
  if (
    isSameRoute(route, current) &&
    route.matched.length === current.matched.length
  ) {
    this.ensureURL()
    return abort()
  }
  // Compare routes to resolve reusable components, components that need to be rendered, and components that need to be inactivated
  const { updated, deactivated, activated } = resolveQueue(
    this.current.matched,
    route.matched
  )
  
  function resolveQueue(current: Array
       
        , next: Array
        
       ) :{
      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++) {
        // The current route path and the forward route path do not jump the traversal at the same time
        if(current[i] ! == next[i]) {break}}return {
        // Reusable components correspond to routes
        updated: next.slice(0, i),
        // The component that needs to be rendered corresponds to the route
        activated: next.slice(i),
        // The deactivated component corresponds to a route
        deactivated: current.slice(i)
      }
  }
  // Navigation guard array
  const queue: Array<? NavigationGuard> = [].concat(// Deactivate the component hook
    extractLeaveGuards(deactivated),
    // global beforeEach hook
    this.router.beforeHooks,
    // Called when the current route changes but the component is being reused
    extractUpdateHooks(updated),
    // Requires the render component Enter guard hook
    activated.map(m= > m.beforeEnter),
    // Parse the asynchronous routing component
    resolveAsyncComponents(activated)
  )
  // Save the route
  this.pending = route
  // Iterator to execute the navigation guard hook in queue
  const iterator = (hook: NavigationGuard, next) = > {
  // Do not jump routes if the routes are not equal
    if (this.pending ! == route) {return abort()
    }
    try {
    // Execute the hook
      hook(route, current, (to: any) => {
        // The next hook function is executed only after next is executed
        // Otherwise the jump will be suspended
        // The following logic is used to judge the passing arguments in next()
        if (to === false || isError(to)) {
          // next(false) 
          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 {
        // Execute next
        // Execute step(index + 1) in runQueue
          next(to)
        }
      })
    } catch (e) {
      abort(e)
    }
  }
  // Classic synchronous execution of asynchronous functions
  runQueue(queue, iterator, () => {
    const postEnterCbs = []
    const isValid = (a)= > this.current === route
    When all asynchronous components are loaded, the cb() callback in runQueue will be executed.
    // Next execute the navguard hook that needs to render the component
    const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)
    const queue = enterGuards.concat(this.router.resolveHooks)
    runQueue(queue, iterator, () => {
    // The jump is complete
      if (this.pending ! == route) {return abort()
      }
      this.pending = null
      onComplete(route)
      if (this.router.app) {
        this.router.app.$nextTick((a)= > {
          postEnterCbs.forEach(cb= > {
            cb()
          })
        })
      }
    })
  })
}
export function runQueue (queue: Array
       , fn: Function, cb: Function) {
  const step = index= > {
  // When all the functions in the queue are finished executing, the callback function is executed
    if (index >= queue.length) {
      cb()
    } else {
      if (queue[index]) {
      // Executes the iterator, and the user executes the next() callback in the hook function
      Next (), the second parameter in the fn function
        fn(queue[index], () => {
          step(index + 1)})}else {
        step(index + 1)}}}// Retrieve the first hook function in the queue
  step(0)}Copy the code

Next, the navigation guard

const queue: Array<? NavigationGuard> = [].concat(// Deactivate the component hook
    extractLeaveGuards(deactivated),
    // global beforeEach hook
    this.router.beforeHooks,
    // Called when the current route changes but the component is being reused
    extractUpdateHooks(updated),
    // Requires the render component Enter guard hook
    activated.map(m= > m.beforeEnter),
    // Parse the asynchronous routing component
    resolveAsyncComponents(activated)
)
Copy the code

The first step is to execute the deactivate component’s hook function

function extractLeaveGuards(deactivated: Array<RouteRecord>) :Array<?Function> {
// Pass in the name of the hook function to execute
  return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true)}function extractGuards(records: Array
       
        , name: string, bind: Function, reverse? : boolean
       ) :Array<?Function> {
  const guards = flatMapComponents(records, (def, instance, match, key) => {
   // Find the corresponding hook function in the component
    const guard = extractGuard(def, name)
    if (guard) {
    // Add a context object to each hook function as the component itself
      return Array.isArray(guard)
        ? guard.map(guard= > bind(guard, instance, match, key))
        : bind(guard, instance, match, key)
    }
  })
  // Reduce the dimension of the array and determine if the array needs to be flipped
  // Some hook functions need to be executed from child to parent
  return flatten(reverse ? guards.reverse() : guards)
}
export function flatMapComponents (matched: Array
       
        , fn: Function
       ) :Array<?Function> {
// Array dimension reduction
  return flatten(matched.map(m= > {
  // Pass the component object into the callback function to get the array of hook functions
    return Object.keys(m.components).map(key= > fn(
      m.components[key],
      m.instances[key],
      m, key
    ))
  }))
}
Copy the code

The second step executes the global beforeEach hook function

beforeEach(fn: Function) :Function {
    return registerHook(this.beforeHooks, fn)
}
function registerHook(list: Array<any>, fn: Function) :Function {
  list.push(fn)
  return (a)= > {
    const i = list.indexOf(fn)
    if (i > - 1) list.splice(i, 1)}}Copy the code

There is this code in the VueRouter class, which pushes the function into beforeHooks whenever you add a beforeEach function to an instance of VueRouter.

The third step is to execute the beforeRouteUpdate hook function, which is called in the same way as the first step but with a different function name, from which the this object is accessed.

The fourth step executes the beforeEnter hook function, which is the exclusive hook function of the route.

The fifth step is to parse the asynchronous component.

export function resolveAsyncComponents (matched: Array<RouteRecord>) :Function {
  return (to, from, next) = > {
    let hasAsync = false
    let pending = 0
    let error = null
    // This function has been introduced before
    flatMapComponents(matched, (def, _, match, key) => {
    // Determine if it is an asynchronous component
      if (typeof def === 'function' && def.cid === undefined) {
        hasAsync = true
        pending++
        // Successful callback
        // The once function ensures that asynchronous components are loaded only once
        const resolve = once(resolvedDef= > {
          if (isESModule(resolvedDef)) {
            resolvedDef = resolvedDef.default
          }
          // Check if it is a constructor
          // If not, use Vue to generate component constructors
          def.resolved = typeof resolvedDef === 'function'
            ? resolvedDef
            : _Vue.extend(resolvedDef)
        // Assign component
        // If all components are parsed, proceed to the next step
          match.components[key] = resolvedDef
          pending--
          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) {
        // The download completes and executes the callback
          if (typeof res.then === 'function') {
            res.then(resolve, reject)
          } else {
            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() } }Copy the code

This is the logic in the first runQueue, and the callback in the first runQueue is executed after step 5

// This callback is used to hold the callback function in the beforeRouteEnter hook
const postEnterCbs = []
const isValid = (a)= > this.current === route
// beforeRouteEnter navigation guard hook
const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)
// beforeResolve navigation guard hook
const queue = enterGuards.concat(this.router.resolveHooks)
runQueue(queue, iterator, () => {
  if (this.pending ! == route) {return abort()
  }
  this.pending = null
  // The afterEach navigation guard hook is executed
  onComplete(route)
  if (this.router.app) {
    this.router.app.$nextTick((a)= > {
      postEnterCbs.forEach(cb= > {
        cb()
      })
    })
  }
})
Copy the code

The sixth step is to execute the beforeRouteEnter navigation guard hook. The beforeRouteEnter hook cannot access this object because the hook is called before navigation confirmation and the component to render has not yet been created. But the hook function is the only one that supports retrieving this in a callback that is executed during route validation.

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

How do we support getting this object in a callback

function extractEnterGuards(activated: Array
       
        , cbs: Array
        
         , isValid: (
        
       ) = >boolean) :Array<?Function> {
// This is basically the same as before
  return extractGuards(
    activated,
    'beforeRouteEnter',
    (guard, _, match, key) => {
      return bindEnterGuard(guard, match, key, cbs, isValid)
    }
  )
}
function bindEnterGuard(guard: NavigationGuard, match: RouteRecord, key: string, cbs: Array
       
        , isValid: (
       ) = >boolean) :NavigationGuard {
  return function routeEnterGuard(to, from, next) {
    return guard(to, from, cb => {
    // check whether cb is a function
    // If yes, push into postEnterCbs
      next(cb)
      if (typeof cb === 'function') {
        cbs.push((a)= > {
          // Loop until you get the component instance
          poll(cb, match.instances, key, isValid)
        })
      }
    })
  }
}
// This function is used to solve issus #750
// The router-View has a transition component with mode out-of-In wrapped around it
// The component instance object will not be available when the component is first navigated
function poll(
  cb: any, // somehow flow cannot infer this is a function
  instances: Object,
  key: string,
  isValid: () = >boolean
) {
  if( instances[key] && ! instances[key]._isBeingDestroyed// do not reuse being destroyed instance
  ) {
    cb(instances[key])
  } else if (isValid()) {
  // setTimeout 16ms has the same effect as nextTick
    setTimeout((a)= > {
      poll(cb, instances, key, isValid)
    }, 16)}}Copy the code

The seventh step is to execute the beforeResolve navigation guard hook, which is executed if the global beforeResolve hook is registered.

The eighth step is navigation validation, which calls the afterEach navigation guard hook.

When all of this is done, it triggers the rendering of the component

history.listen(route= > {
      this.apps.forEach(app= > {
        app._route = route
      })
})
Copy the code

The above callbacks are called in updateRoute

updateRoute(route: Route) {
    const prev = this.current
    this.current = route
    this.cb && this.cb(route)
    this.router.afterHooks.forEach(hook= > {
      hook && hook(route, prev)
    })
}
Copy the code

At this point, the analysis of route hops is complete. The core is to determine whether the route to jump exists in the record, then perform various navigational guard functions, and finally complete the URL change and component rendering.

To apply for a job

Recently I am looking for job opportunities, if there is a good position in Hangzhou, please contact me at [email protected].

The public,

The last

If you don’t know anything or think I made a mistake, please feel free to comment.

Related articles

  • In-depth framework origin series – Virtual Dom
  • Deep analysis of Vue response formula principle