Deep analysis of vueroter source code

Posted by n8r0x on Tue, 08 Feb 2022 02:35:43 +0100

Routing principle

Before analyzing the source code, let's understand the implementation principle of front-end routing. The implementation of front-end routing is actually very simple. Its essence is to monitor the change of URL, then match the routing rules, display the corresponding page, and there is no need to refresh. At present, there are only two ways to realize the routing used by a single page

  • hash mode
  • history mode

www.test.com / # / is the Hash URL. When # the following hash value changes, it will not request data from the server. You can monitor the change of URL through hashchange event to jump to the page.

History mode is a new feature of HTML5, which is more beautiful than Hash URL

Vueroter source code analysis

Route registration

Before we start, we recommend you to compare a copy of clone source code. Because of the long space, there are many jumps between functions.

Before using routing, you need to call Vue Use (vuerouter), which is because Vue can be used by plug-ins

export function initUse (Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | Object) {
    // Determine whether the plug-in is installed repeatedly
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }
    const args = toArray(arguments, 1)
    // Insert Vue
    args.unshift(this)
    // Generally, plug-ins have an install function
    // This function enables plug-ins to use Vue
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
  }
}

Next, let's look at the partial implementation of the install function

export function install (Vue) {
  // Ensure that install is called once
  if (install.installed && _Vue === Vue) return
  install.installed = true
  // Assign Vue to 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)
    }
  }
  // Mixed implementation of hook function for each component
  // It can be found that when the 'beforeCreate' hook is executed
  // The route is initialized
  Vue.mixin({
    beforeCreate () {
      // Judge 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 route
        this._router.init(this)
        // Very important, for_ The route attribute implements bidirectional binding
        // Trigger component rendering
        Vue.util.defineReactive(this, '_route', this._router.history.current)
      } else {
        // Used for router view level judgment
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
      }
      registerInstance(this, this)
    },
    destroyed () {
      registerInstance(this)
    }
  })
  // Global registration components router link and router view
  Vue.component('RouterView', View)
  Vue.component('RouterLink', Link)
}

For route registration, the core is to call Vue Use (VueRouter), so that VueRouter can use Vue. Then call the install function of vueroter through Vue. In this function, the core is to mix two routing components: hook function and global registration.

Vueroter instantiation

After installing the plug-in, instantiate vueroter.

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 }
  ]
})

Take a look at the constructor of vueroter

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

    // Different routing methods are adopted according to the 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}`)
        }
    }
  }

In the process of instantiating vueroter, the core is to create a route matching object and adopt different routing methods according to the mode.

Create route matching object

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

  return {
    match,
    addRoutes
  }
}

The function of createMatcher is to create a routing mapping table, and then enable addRoutes and match functions to use several objects of the routing mapping table through closure, and finally return a Matcher object.

Next, let's look at how to create a mapping table when creating the createMatcher function

export function createRouteMap (
  routes: Array<RouteConfig>,
  oldPathList?: Array<string>,
  oldPathMap?: Dictionary<RouteRecord>,
  oldNameMap?: Dictionary<RouteRecord>
): {
  pathList: Array<string>;
  pathMap: Dictionary<RouteRecord>;
  nameMap: Dictionary<RouteRecord>;
} {
  // Create mapping table
  const pathList: Array<string> = oldPathList || []
  const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null)
  const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null)
  // Traverse the routing configuration and add routing records 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] === '*') {
      pathList.push(pathList.splice(i, 1)[0])
      l--
      i--
    }
  }
  return {
    pathList,
    pathMap,
    nameMap
  }
}
// Add routing record
function addRouteRecord (
  pathList: Array<string>,
  pathMap: Dictionary<RouteRecord>,
  nameMap: Dictionary<RouteRecord>,
  route: RouteConfig,
  parent?: RouteRecord,
  matchAs?: string
) {
  // Get properties under routing configuration
  const { path, name } = route
  const pathToRegexpOptions: PathToRegexpOptions = route.pathToRegexpOptions || {}
  // Format url, replace/ 
  const normalizedPath = normalizePath(
    path,
    parent,
    pathToRegexpOptions.strict
  )
  // Generate 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) {
    // The children attribute of recursive routing configuration and add routing records
    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 routing records to the alias as well
  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 mapping table
  if (!pathMap[record.path]) {
    pathList.push(record.path)
    pathMap[record.path] = record
  }
  // Add record to 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}" }`
      )
    }
  }
}

The above is the whole process of creating route matching objects. The corresponding route mapping table is created through the user configured route 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 route will be initialized when the root component is initialized
  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)
}

Next, let's see what route initialization will do

init(app: any /* Vue component instance */) {
    // Save component instance
    this.apps.push(app)
    // If the root component already exists, return
    if (this.app) {
      return
    }
    this.app = app
    // Assignment routing mode
    const history = this.history
    // Judge the routing mode, taking the hash mode as an example
    if (history instanceof HTML5History) {
      history.transitionTo(history.getCurrentLocation())
    } else if (history instanceof HashHistory) {
      // Add hashchange listener
      const setupHashListener = () => {
        history.setupListeners()
      }
      // Route jump
      history.transitionTo(
        history.getCurrentLocation(),
        setupHashListener,
        setupHashListener
      )
    }
    // The callback will be invoked in transitionTo.
    // Of components_ Assign a value to the route attribute to trigger component rendering
    history.listen(route => {
      this.apps.forEach(app => {
        app._route = route
      })
    })
  }

During route initialization, the core is to jump the route, change the URL, and then render the corresponding components. Next, let's take a look at how the route jumps.

Route jump

transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {
  // Get matching routing information
  const route = this.router.match(location, this.current)
  // Confirm switching route
  this.confirmTransition(route, () => {
    // The following are callbacks for successful or failed route switching
    // Update the routing information and modify the components_ Assign a value to the route attribute to trigger component rendering
    // Call the hook function in afterHooks
    this.updateRoute(route)
    // Add hashchange listener
    onComplete && onComplete(route)
    // Update 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) })
    }
  })
}

In route jump, you need to get the matching route information first, so let's see how to get the matching route information first

function match (
  raw: RawLocation,
  currentRoute?: Route,
  redirectedFrom?: Location
): Route {
  // Serialize url
  // For example, / ABC? foo=bar&baz=qux#hello
  // The serialization path is / abc
  // Hash to #hello
  // The parameters are foo: 'bar', baz: 'qux'
  const location = normalizeLocation(raw, currentRoute, false, router)
  const { name } = location
  // If it is a named route, judge whether there is the named route configuration in the record
  if (name) {
    const record = nameMap[name]
    // No route found indicating no match
    if (!record) return _createRoute(null, location)
    const paramNames = record.regex.keys
      .filter(key => !key.optional)
      .map(key => key.name)
    // Parameter processing
    if (typeof location.params !== 'object') {
      location.params = {}
    }
    if (currentRoute && typeof currentRoute.params === 'object') {
      for (const key in currentRoute.params) {
        if (!(key in location.params) && paramNames.indexOf(key) > -1) {
          location.params[key] = currentRoute.params[key]
        }
      }
    }
    if (record) {
      location.path = fillParams(record.path, location.params, `named route "${name}"`)
      return _createRoute(record, location, redirectedFrom)
    }
  } else if (location.path) {
    // Unnamed routing processing
    location.params = {}
    for (let i = 0; i < pathList.length; i++) {
     // find 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)
      }
    }
  }
  // No matching route
  return _createRoute(null, location)
}

Next, let's look at how to create a route

// Create different routes according to conditions
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 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)
  }
  // Make the routing object unmodifiable
  return Object.freeze(route)
}
// Get the route record containing all nested path segments 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
}

So far, the matching route has been completed. Let's return to the transitionTo function, and then execute confirmTransition

transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {
  // Confirm switching route
  this.confirmTransition(route, () => {}
}
confirmTransition(route: Route, onComplete: Function, onAbort?: Function) {
  const current = this.current
  // Interrupt jump 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)
  }
  // If it is the same route, it will not jump
  if (
    isSameRoute(route, current) &&
    route.matched.length === current.matched.length
  ) {
    this.ensureURL()
    return abort()
  }
  // By comparing routes, analyze reusable components, components to be rendered and inactive components
  const { updated, deactivated, activated } = resolveQueue(
    this.current.matched,
    route.matched
  )
  
  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++) {
        // When the current routing path and the jump routing path are different, jump out of traversal
        if (current[i] !== next[i]) {
          break
        }
      }
      return {
        // Routes corresponding to reusable components
        updated: next.slice(0, i),
        // Route corresponding to the component to be rendered
        activated: next.slice(i),
        // Route corresponding to inactive components
        deactivated: current.slice(i)
      }
  }
  // Navigation guard array
  const queue: Array<?NavigationGuard> = [].concat(
    // Inactive component hook
    extractLeaveGuards(deactivated),
    // Global beforeEach hook
    this.router.beforeHooks,
    // Called when the current route changes but the component is reused
    extractUpdateHooks(updated),
    // Need render component enter guard hook
    activated.map(m => m.beforeEnter),
    // Parsing asynchronous routing components
    resolveAsyncComponents(activated)
  )
  // Save route
  this.pending = route
  // Iterator, which is used to execute the navigation guard hook in the queue
  const iterator = (hook: NavigationGuard, next) => {
  // If the routes are not equal, the route will not jump
    if (this.pending !== route) {
      return abort()
    }
    try {
    // Execution hook
      hook(route, current, (to: any) => {
        // The next hook function will not be executed until next in the hook function is executed
        // Otherwise, the jump will be suspended
        // The following logic is used to determine the parameters passed 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 here
        // That is, execute the step(index + 1) in the following function runQueue
          next(to)
        }
      })
    } catch (e) {
      abort(e)
    }
  }
  // Classic synchronous execution asynchronous function
  runQueue(queue, iterator, () => {
    const postEnterCbs = []
    const isValid = () => this.current === route
    // When all asynchronous components are loaded, the callback here will be executed, that is, cb() in runQueue
    // Next, execute the navigation guard hook that needs to render the component
    const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)
    const queue = enterGuards.concat(this.router.resolveHooks)
    runQueue(queue, iterator, () => {
    // Jump complete
      if (this.pending !== route) {
        return abort()
      }
      this.pending = null
      onComplete(route)
      if (this.router.app) {
        this.router.app.$nextTick(() => {
          postEnterCbs.forEach(cb => {
            cb()
          })
        })
      }
    })
  })
}
export function runQueue (queue: Array<?NavigationGuard>, fn: Function, cb: Function) {
  const step = index => {
  // After all the functions in the queue are executed, the callback function is executed
    if (index >= queue.length) {
      cb()
    } else {
      if (queue[index]) {
      // The iterator is executed, and the user executes the next() callback in the hook function
      // If there is no problem, execute next(), that is, the second parameter in fn function
        fn(queue[index], () => {
          step(index + 1)
        })
      } else {
        step(index + 1)
      }
    }
  }
  // Get the first hook function in the queue
  step(0)
}

Next, the navigation guard is introduced

const queue: Array<?NavigationGuard> = [].concat(
    // Inactive component hook
    extractLeaveGuards(deactivated),
    // Global beforeEach hook
    this.router.beforeHooks,
    // Called when the current route changes but the component is reused
    extractUpdateHooks(updated),
    // Need render component enter guard hook
    activated.map(m => m.beforeEnter),
    // Parsing asynchronous routing components
    resolveAsyncComponents(activated)
)

The first step is to execute the hook function of the inactivated component first

function extractLeaveGuards(deactivated: Array<RouteRecord>): Array<?Function> {
// Pass in the name of the hook function to be executed
  return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true)
}
function extractGuards(
  records: Array<RouteRecord>,
  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 for 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 judge whether to flip the array
  // Because some hook functions need to be executed from the child to the parent
  return flatten(reverse ? guards.reverse() : guards)
}
export function flatMapComponents (
  matched: Array<RouteRecord>,
  fn: Function
): Array<?Function> {
// Array dimensionality reduction
  return flatten(matched.map(m => {
  // Pass the object in the component into the callback function to obtain the hook function array
    return Object.keys(m.components).map(key => fn(
      m.components[key],
      m.instances[key],
      m, key
    ))
  }))
}

Step 2: execute 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 () => {
    const i = list.indexOf(fn)
    if (i > -1) list.splice(i, 1)
  }
}

There is the above code in the vueroter class. Whenever the beforeEach function is added to the vueroter instance, the function will be push ed into beforeHooks.

The third step is to execute the beforeRouteUpdate hook function. The calling method is the same as that in the first step, except that the passed in function name is different. This object can be accessed in this function.

Step 4: execute the beforeEnter hook function, which is the exclusive hook function for routing.

The fifth step is to parse asynchronous components.

export function resolveAsyncComponents (matched: Array<RouteRecord>): Function {
  return (to, from, next) => {
    let hasAsync = false
    let pending = 0
    let error = null
    // The function has been introduced before
    flatMapComponents(matched, (def, _, match, key) => {
    // Determine whether it is an asynchronous component
      if (typeof def === 'function' && def.cid === undefined) {
        hasAsync = true
        pending++
        // Successful callback
        // The once function ensures that the asynchronous component is loaded only once
        const resolve = once(resolvedDef => {
          if (isESModule(resolvedDef)) {
            resolvedDef = resolvedDef.default
          }
          // Determine whether it is a constructor
          // If not, generate the component constructor through Vue
          def.resolved = typeof resolvedDef === 'function'
            ? resolvedDef
            : _Vue.extend(resolvedDef)
        // Assignment component
        // If all components are resolved, continue 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 {
        // Executing asynchronous component functions
          res = def(resolve, reject)
        } catch (e) {
          reject(e)
        }
        if (res) {
        // Download complete execution 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 an asynchronous component, go to the next step directly
    if (!hasAsync) next()
  }
}

The above is the logic in the first runQueue. After the fifth step is completed, the callback function in the first runQueue will be executed

// This callback is used to save the callback function in the 'beforeRouteEnter' hook
const postEnterCbs = []
const isValid = () => 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
  // After each navigation guard hook will be executed here
  onComplete(route)
  if (this.router.app) {
    this.router.app.$nextTick(() => {
      postEnterCbs.forEach(cb => {
        cb()
      })
    })
  }
})

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 components to be rendered have not been created. However, this hook function is the only one that supports obtaining this object in the callback, which will be executed after route confirmation.

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

Let's take a look at how to get this object in the callback

function extractEnterGuards(
  activated: Array<RouteRecord>,
  cbs: Array<Function>,
  isValid: () => boolean
): Array<?Function> {
// This is basically the same as before calling the navigation guard.
  return extractGuards(
    activated,
    'beforeRouteEnter',
    (guard, _, match, key) => {
      return bindEnterGuard(guard, match, key, cbs, isValid)
    }
  )
}
function bindEnterGuard(
  guard: NavigationGuard,
  match: RouteRecord,
  key: string,
  cbs: Array<Function>,
  isValid: () => boolean
): NavigationGuard {
  return function routeEnterGuard(to, from, next) {
    return guard(to, from, cb => {
    // Judge whether cb is a function
    // If yes, push into postEnterCbs
      next(cb)
      if (typeof cb === 'function') {
        cbs.push(() => {
          // Loop until you get the component instance
          poll(cb, match.instances, key, isValid)
        })
      }
    })
  }
}
// This function is used to solve the problem of issus #750
// When the router view is wrapped with the transition component with mode out in 
// When the component navigates for the first time, the component instance object cannot be obtained
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 is basically the same as nextTick
    setTimeout(() => {
      poll(cb, instances, key, isValid)
    }, 16)
  }
}

The seventh step is to execute the beforeResolve navigation guard hook. If the global beforeResolve hook is registered, it will be executed here.

The eighth step is to confirm the navigation and call the afterEach navigation guard hook.

After the above execution is completed, the rendering of components will be triggered

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

The callback will be invoked 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)
    })
}

So far, the route jump has been fully analyzed. The core is to judge whether the route to jump exists in the record, then execute various navigation guard functions, and finally complete the URL change and component rendering.

 

 

 

Topics: Vue