A few days ago I saw a question: Do you really know vue-Router? Do you know how vue-Router works? Holding such a problem, the author began the vue-Router source code exploration journey. This article does not delve into the source code line by line, but follows the author to draw the flow chart to analyze each step of the operation process.

Analyze the operation process

The author according to the structure of the source code and their own understanding of the advance draw a flow chart, at first glance this running flow chart may be a little bit of a circle, the author will now run the process according to the analysis of this figure, and then step by step analysis of the core part of the source code.

  • Under the $optionsrouterThis is the vue-Router instance we mounted when we instantiated Vue;
  • _routeDefineReactive is a reactive route object that stores our routing information. It is reactive via vue.util.definereActive provided by Vue. The following get and set are data hijacking for it.
  • _routerIt stores the vue-Router object we got from $options;
  • _routerRootPoint to our Vue root node;
  • _routerViewCacheIt’s our cache of the View;
  • $routeand$routerGetters are two getters defined on Vue.prototype. The former points to _Route under _routerRoot and the latter to _Router under _routerRoot

Let’s go through this “dazzling graph” so we can better understand the source code analysis later.

First, we installed vue-Router according to the plugin mechanism of Vue, which is very simple, summed up is to encapsulate a mixin, define two ‘prototypes’, and register two components. In this mixin, the beforeCreate hook is called and determines whether vue-Router is instantiated and initializes the routing logic, where the _routerRoot, _Router, and _route are defined. Prototype defines two getters on vue. prototype for $route and $router. Registering two components means registering the RouterView and RouterLink components that we will use later.

We then create an instance of VueRouter and mount it on an instance of Vue, where constructor initializes the various hook queues; Initialize matcher to do our routing matching logic and create routing objects; History is initialized to perform the transition logic and execute the hook queue.

The other thing that beforeCreate does in the next mixin is perform the init() method of our VueRouter instance to initialize it, which is similar to the RouteLink or functional control route we clicked on, which I’ll cover here. Init calls the transitionTo method of the history object, and then uses match to get the current route match and creates a new route object, route. Next, take the Route object and execute the confirmTransition method to execute the hook queue events. Finally, update current, the object storing the current route data, with updateRoute to point to the route object we just created.

In the beginning we said that _route was defined to be reactive so that when a route is updated, the _route object receives the response and tells the RouteView to update its view.

At this point, the process is over, and next we will dive into vue-Router’s source code to learn how it works.

Analyze the source

Said in the previous

Vue-router source code uses flow as a type check, if not configured flow may be full screen error, this article will not do too much introduction to flow. In order to facilitate your understanding, I will remove the syntax related to flow in the following source code section. By the way, some flow related:

Official document flow (need science online) : https://flow.org/ flow introduction: https://zhuanlan.zhihu.com/p/26204569 flow configuration: https://zhuanlan.zhihu.com/p/24649359

The project structure

When getting the source code for a project, we first need to look at its directory structure:

  • RouterLink and RouterView are componets.
  • Create-matcher. js is the entry file where we create match;
  • Create-route-map. js is used to create a path list, path map, name map, etc.
  • History is the logic for creating the Hitory class;
  • Index.js is our entry file, where we created the VueRouter class;
  • Install.js is our logic for mounting vue-Router plug-ins;
  • Util defines many utility functions;

Application gateways

Usually when we build a Vue application the entry file will say something like this:

// app.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import Main from '.. /components/main';

Vue.use(VueRouter);

const router = new VueRouter({
  routes: [{
    path: '/'.component: Main,
  }],
});

// app.js
new Vue({
  router,
  template,
}).$mount('#app')
Copy the code

We can see that vue-Router is installed as a plug-in, and the vue-Router instance is mounted on top of the vue instance.

Plug-in installation

At this point we look at the source entry file and see that the install module has been introduced in index.js and a static install method has been mounted on the VueRouter class. It also determines that the environment will automatically use the plug-in if Vue is already mounted.

/ SRC /index.js

import { install } from './install'
import { inBrowser } from './util/dom'
// ...
export default class VueRouter {}
// ...
// Mount install;
VueRouter.install = install
// Check that the plugin is automatically used if the window has Vue mounted;
if (inBrowser && window.Vue) {
  window.Vue.use(VueRouter)
}
Copy the code

Next look at the install.js file, which exports the export method for vue.use to install:

Source: / SRC /install.js

import View from './components/view'
import Link from './components/link'

// The reason for exporting a Vue is that you can use some methods of Vue without packing it into a plug-in;
// An instance of this Vue can only exist after install;
export let _Vue

export function install (Vue) {
  // Return if the plug-in is already installed
  if (install.installed && _Vue === Vue) return
  install.installed = true

  _Vue = Vue

  const isDef = v= >v ! = =undefined

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

  Vue.mixin({
    beforeCreate () {
      // this.$options. Router is the instance of VueRouter;
      // Check whether the instance is already mounted;
      if (isDef(this.$options.router)) {
        // Point the root component of the router to the Vue instance
        this._routerRoot = this
        this._router = this.$options.router
        // Call VueRouter's init method to initialize the router;
        this._router.init(this)
        // Add a _route response object using Vue defineReactive
        Vue.util.defineReactive(this.'_route'.this._router.history.current)
      } else {
        // Point each component's _routerRoot to the root Vue instance;
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
      }
      // Register VueComponent for Observer processing;
      registerInstance(this.this)
    },
    destroyed () {
      / / logout VueComponent
      registerInstance(this)}})Define << getter >> for $router and 4route to point to _router and _route of _routerRoot, respectively
  // _router is an instance of VueRouter;
  // _route is an object that stores routing data;
  Object.defineProperty(Vue.prototype, '$router', {
    get () { return this._routerRoot._router }
  })

  Object.defineProperty(Vue.prototype, '$route', {
    get () { return this._routerRoot._route }
  })

  // Register the component
  Vue.component('RouterView', View)
  Vue.component('RouterLink', Link)

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

A few points to note here:

  • Export a Vue reference: This is to use some of the Vue apis without having to package the entire Vue. Of course, this is provided if vue-Router is installed and mounted.
  • Define two getters on Vue. Prototype: The Vue component is an extension of the Vue instance, and both have access to the prototype methods and properties;
  • Define a reactive _route object: With this reactive route object, RouterView can be notified to update the component when the route is updated.

Instantiation VueRouter

Now let’s look at instantiating the VueRouter class. Constructor does two main things: create matcher and create history:

/ SRC /index.js

// ...
import { createMatcher } from './create-matcher'
import { supportsPushState } from './util/push-state'
import { HashHistory } from './history/hash'
import { HTML5History } from './history/html5'
import { AbstractHistory } from './history/abstract'
// ...
export default class VueRouter {
  constructor (options) {
    this.app = null
    this.apps = []
    // VueRouter configuration item;
    this.options = options
    // Three hooks
    this.beforeHooks = []
    this.resolveHooks = []
    this.afterHooks = []
    // Create a route matching instance; Pass on the routes we defined: the object containing the path and component;
    this.matcher = createMatcher(options.routes || [], this)
    // Determine the mode
    let mode = options.mode || 'hash'
    // Check whether the browser supports history. If not, revert to hash mode.
    this.fallback = mode === 'history'&&! supportsPushState && options.fallback ! = =false
    if (this.fallback) {
      mode = 'hash'
    }
    // Node running environment mode = 'abstract';
    if(! inBrowser) { mode ='abstract'
    }
    this.mode = mode
    // Create the corresponding history instance based on the pattern
    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

Create the matcher

Let’s look at the createMatcher function:

/ SRC /create-matcher.js

import VueRouter from './index'
import { resolvePath } from './util/path'
import { assert, warn } from './util/warn'
import { createRoute } from './util/route'
import { fillParams } from './util/params'
import { createRouteMap } from './create-route-map'
import { normalizeLocation } from './util/location'

// routes initializes the VueRouter route configuration for us;
// Router is our instance of VueRouter;
export function createMatcher (routes, router) {
  // pathList is an array of paths generated from routes;
  // pathMap is a map generated based on the name of path;
  // If we define name on the route configuration, then we have a Map of name;
  const { pathList, pathMap, nameMap } = createRouteMap(routes)
  // Generate a route based on the new routes;
  function addRoutes (routes) {
    createRouteMap(routes, pathList, pathMap, nameMap)
  }
  // Route matching function;
  function match (raw, currentRoute, redirectedFrom) {
    Path params query;
    const location = normalizeLocation(raw, currentRoute, false, router)
    const { name } = location

    if (name) {
      // If there is a name, go to the name map to find the route record;
      const record = nameMap[name]
      if(process.env.NODE_ENV ! = ='production') {
        warn(record, `Route with name '${name}' does not exist`)}// Create a routing object if there is no routing record;
      if(! record)return _createRoute(null, location)
      const paramNames = record.regex.keys
        .filter(key= >! key.optional) .map(key= > key.name)

      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) {
      location.params = {}
      for (let i = 0; i < pathList.length; i++) {
        const path = pathList[i]
        const record = pathMap[path]
        // Match routes based on the current path
        // Create a route object if it matches;
        if (matchRoute(record.regex, location.path, location.params)) {
          return _createRoute(record, location, redirectedFrom)
        }
      }
    }
    // no match
    return _createRoute(null, location)
  }
  
  // ...

  function _createRoute (record, location, redirectedFrom) {
    // Create routing objects according to different conditions;
    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)
  }

  return {
    match,
    addRoutes
  }
}

function matchRoute (regex, path, params) {
  const m = path.match(regex)

  if(! m) {return false
  } else if(! params) {return true
  }

  for (let i = 1, len = m.length; i < len; ++i) {
    const key = regex.keys[i - 1]
    const val = typeof m[i] === 'string' ? decodeURIComponent(m[i]) : m[i]
    if (key) {
      params[key.name] = val
    }
  }

  return true
}

function resolveRecordPath (path, record) {
  return resolvePath(path, record.parent ? record.parent.path : '/'.true)}Copy the code

First, createMatcher will generate a map with corresponding relationships based on the routes configuration defined when we initialize the VueRouter instance. The logic will be described below. Then return a match object containing two methods: match and addRoutes, which is our detailed logic to implement the route matching, it will return the matched route object; AddRoutes would be the way to addRoutes.

Create -route-map.js

/ SRC /create-route-map.js

/* @flow */

import Regexp from 'path-to-regexp'
import { cleanPath } from './util/path'
import { assert, warn } from './util/warn'

export function createRouteMap (routes, oldPathList, oldPathMap, oldNameMap) {
  // the path list is used to control path matching priority
  const pathList = oldPathList || []
  // $flow-disable-line
  const pathMap = oldPathMap || Object.create(null)
  // $flow-disable-line
  const nameMap = oldNameMap || Object.create(null)
  / / the path list
  // Map of path
  // Map mapping of name
  // Add a route record to the configured route entry
  routes.forEach(route= > {
    addRouteRecord(pathList, pathMap, nameMap, route)
  })

  // ensure wildcard routes are always 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 an object containing the path array, path map, and name map;
  return {
    pathList,
    pathMap,
    nameMap
  }
}

function addRouteRecord (pathList, pathMap, nameMap, route, parent, matchAs) {
  const { path, name } = route
  if(process.env.NODE_ENV ! = ='production') { assert(path ! =null.`"path" is required in a route configuration.`)
    assert(
      typeofroute.component ! = ='string'.`route config "component" for path: The ${String(path || name)} cannot be a ` +
      `string id. Use an actual component instead.`)}// Define the path to Reg option;
  const pathToRegexpOptions: PathToRegexpOptions = route.pathToRegexpOptions || {}
  // serialize path, '/' will be replaced with '';
  const normalizedPath = normalizePath(
    path,
    parent,
    pathToRegexpOptions.strict
  )

  // Whether the re match is case sensitive;
  if (typeof route.caseSensitive === 'boolean') {
    pathToRegexpOptions.sensitive = route.caseSensitive
  }

  const record = {
    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 there are nested child routes, add route records recursively;
  if (route.children) {
    // Warn if route is named, does not redirect and has a default child route.
    // If users navigate to this route by name, the default child will
    // not be rendered (GH Issue #629)
    if(process.env.NODE_ENV ! = ='production') {
      if(route.name && ! route.redirect && route.children.some(child= >^ / / /? $/.test(child.path))) { warn(false.`Named Route '${route.name}' has a default child route. ` +
          `When navigating to this named route (:to="{name: '${route.name}'"), ` +
          `the default child route will not be rendered. Remove the name from ` +
          `this route and use the name of the default child route for named ` +
          `links instead.`
        )
      }
    }
    route.children.forEach(child= > {
      const childMatchAs = matchAs
        ? cleanPath(`${matchAs}/${child.path}`)
        : undefined
      addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)
    })
  }

  // If the route contains an alias, add an alias route record to the route
  / / about alias
  // https://router.vuejs.org/zh-cn/essentials/redirect-and-alias.html
  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 path map
  if(! pathMap[record.path]) { pathList.push(record.path) pathMap[record.path] = record }// Update the name map for the route with name defined
  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}" }`)}}}function compileRouteRegex (path, pathToRegexpOptions) {
  const regex = Regexp(path, [], pathToRegexpOptions)
  if(process.env.NODE_ENV ! = ='production') {
    const keys: any = Object.create(null)
    regex.keys.forEach(key= >{ warn(! keys[key.name],`Duplicate param keys in route with path: "${path}"`)
      keys[key.name] = true})}return regex
}

function normalizePath (path, parent, strict) :string {
  if(! strict) path = path.replace(/ / / $/.' ')
  if (path[0= = ='/') return path
  if (parent == null) return path
  return cleanPath(`${parent.path}/${path}`)}Copy the code

As can be seen from the above code, create-route-map.js generates routing records according to the path, alias, and name configured on the user routes.

To create the history

There are 4 files in the History folder. Base is the base class, and the other three inherit this base class to handle the various modes of vue-Router. Here we mainly look at the logic of base.

// Install Vue everywhere to avoid packing Vue into projects to increase volume;
import { START, isSameRoute } from '.. /util/route'

export class History {
  constructor (router, base) {
    this.router = router
    this.base = normalizeBase(base)
    // start with a route object that stands for "nowhere"
    // Generate a basic route object;
    this.current = START
    this.pending = null
    this.ready = false
    this.readyCbs = []
    this.readyErrorCbs = []
    this.errorCbs = []
  }
  // ...
}
// ...
function normalizeBase (base: ? string) :string {
  if(! base) {if (inBrowser) {
      // respect <base> tag
      const baseEl = document.querySelector('base')
      base = (baseEl && baseEl.getAttribute('href')) || '/'
      // strip full URL origin
      base = base.replace(/^https? : \ \ [^ \] / / / + /.' ')}else {
      base = '/'}}// make sure there's the starting slash
  if (base.charAt(0)! = ='/') {
    base = '/' + base
  }
  // remove trailing slash
  return base.replace(/ / / $/.' ')}Copy the code

Now that we’ve covered the basics of mounting and various instantiations, we can start with init to see what happens next.

/ SRC /index.js

// ...init (app) { process.env.NODE_ENV ! = ='production' && assert(
      install.installed,
      `not installed. Make sure to call \`Vue.use(VueRouter)\` ` +
      `before creating root instance.`
    )
    // As we know from the calls in Install, this app is the vVue instance we instantiate;
    this.apps.push(app)

    // main app already initialized.
    if (this.app) {
      return
    }
    // Point the app in VueRouter to our Vue instance;
    this.app = app

    const history = this.history
    // Special handling for HTML5History and HashHistory,
    // Because it is possible to enter a page that is not the default in these two modes,
    // Activate the route according to the path or hash in the address bar of the current browser
    if (history instanceof HTML5History) {
      history.transitionTo(history.getCurrentLocation())
    } else if (history instanceof HashHistory) {
      const setupHashListener = (a)= > {
        history.setupListeners()
      }
      history.transitionTo(
        history.getCurrentLocation(),
        setupHashListener,
        setupHashListener
      )
    }
    / /...
  }
// ...
Copy the code

It can be seen that initialization is mainly to assign values to app, and special processing is carried out for HTML5History and HashHistory, because only in these two modes can there be a default page when entering, and the corresponding route needs to be activated according to the path or hash in the address bar of the current browser. This is done by calling transitionTo;

Let’s look at the transitionTo concretely:

Source: / SRC /history/base.js

transitionTo (location, onComplete, onAbort) {
    // localtion is the route to our current page;
    // Call VueRouter's match method to obtain the matched route object and create the next state of the route object;
    // this.current is the current state of the route object we save;
    const route = this.router.match(location, this.current)
    this.confirmTransition(route, () => {
      // Update the current route object;
      this.updateRoute(route)
      onComplete && onComplete(route)
      // Call the subclass method to update the URL
      this.ensureURL()
      // fire ready cbs once
      // Call the ready callback after success;
      if (!this.ready) {
        this.ready = true
        this.readyCbs.forEach(cb= > { cb(route) })
      }
    }, err => {
      if (onAbort) {
        onAbort(err)
      }
      // Call the failed err callback;
      if (err && !this.ready) {
        this.ready = true
        this.readyErrorCbs.forEach(cb= > { cb(err) })
      }
    })
  }
  confirmTransition (route, onComplete, onAbort) {
    const current = this.current
    const abort = err= > {
      if (isError(err)) {
        if (this.errorCbs.length) {
          this.errorCbs.forEach(cb= > { cb(err) })
        } else {
          warn(false.'uncaught error during route navigation:')
          console.error(err)
        }
      }
      onAbort && onAbort(err)
    }
    // If it is the same route, do not jump;
    if (
      isSameRoute(route, current) &&
      // in the case the route map has been dynamically appended to
      route.matched.length === current.matched.length
    ) {
      // Call the subclass method to update the URL
      this.ensureURL()
      return abort()
    }
    // Cross-compares the routing record of the current route with that of the current route
    // In order to be able to accurately get parent-child routing updates can be exactly known
    // Which components need to be updated and which do not
    const {
      updated,
      deactivated,
      activated
    } = resolveQueue(this.current.matched, route.matched)
    // Note that it stores an array of routing records;

    // // The queue for the entire switch cycle, and the various hook update queues to be executed
    const queue: Array<? NavigationGuard> = [].concat(// in-component leave guards
      // Extract the component's beforeRouteLeave hook
      extractLeaveGuards(deactivated),
      // global before hooks
      this.router.beforeHooks,
      // in-component update hooks
      // Extract the component's beforeRouteUpdate hook
      extractUpdateHooks(updated),
      // in-config enter guards
      activated.map(m= > m.beforeEnter),
      // async components
      // Process components asynchronously
      resolveAsyncComponents(activated)
    )
    // Save the route to the next state
    this.pending = route
    // The iterator function executed by each queue
    const iterator = (hook: NavigationGuard, next) = > {
      if (this.pending ! == route) {return abort()
      }
      try {
        hook(route, current, (to: any) => {
          if (to === false || isError(to)) {
            // next(false) -> abort navigation, ensure current URL
            this.ensureURL(true)
            abort(to)
          } else if (
            typeof to === 'string'| | -typeof to === 'object' && (
              typeof to.path === 'string' ||
              typeof to.name === 'string'))) {// next('/') or next({ path: '/' }) -> redirect
            abort()
            if (typeof to === 'object' && to.replace) {
              this.replace(to)
            } else {
              this.push(to)
            }
          } else {
            // confirm transition and pass on the value
            next(to)
          }
        })
      } catch (e) {
        abort(e)
      }
    }
    // Execute various hook queues
    runQueue(queue, iterator, () => {
      const postEnterCbs = []
      const isValid = (a)= > this.current === route
      // wait until async components are resolved before
      // extracting in-component enter guards
      Execute the hook in the component while waiting for the asynchronous component to be OK
      const enterGuards = extractEnterGuards(activated, postEnterCbs, isValid)
      const queue = enterGuards.concat(this.router.resolveHooks)
      // Execute the hook in the component after the last queue execution
      // Because you need to wait for asynchronous components and OK to execute
      runQueue(queue, iterator, () => {
        if (this.pending ! == route) {return abort()
        }
        // Route transition is complete
        this.pending = null
        onComplete(route)
        if (this.router.app) {
          this.router.app.$nextTick((a)= > {
            postEnterCbs.forEach(cb= > { cb() })
          })
        }
      })
    })
  }
  updateRoute (route) {
    const prev = this.current
    // Point current to our updated route object;
    this.current = route
    this.cb && this.cb(route)
    this.router.afterHooks.forEach(hook= > {
      hook && hook(route, prev)
    })
  }
Copy the code

The logic seems complicated, but in fact it is the back-and-forth processing of various hook functions, but it is important to note that every route route object has a Matchd property, which contains a route record, which is generated in create-matcher.js.

Wait a minute, I think we missed something, init left something behind:

/ SRC /index.js

// Set the listener for route changes.
history.listen(route= > {
    this.apps.forEach((app) = > {
        app._route = route
    })
})
Copy the code

This is where the route change callback is called in the onComplete callback from confirmTransition and updates the current _route value. As mentioned earlier, _route is responsive, The component will be notified to rerender when it is updated.

Two components

RouterView: / SRC /components/view.js

import { warn } from '.. /util/warn'

export default {
  name: 'RouterView'.functional: true.props: {
    // The attempt name, default is default
    name: {
      type: String.default: 'default'
    }
  },
  render (_, { props, children, parent, data }) {
    data.routerView = true
    // directly use parent context's createElement() function
    // so that components rendered by router-view can resolve named slots
    // Render function
    const h = parent.$createElement
    const name = props.name
    // get the _route object and cache object;
    const route = parent.$route
    const cache = parent._routerViewCache || (parent._routerViewCache = {})
    // determine current view depth, also check to see if the tree
    // has been toggled inactive but kept-alive.
    // Component level
    // Terminates the loop when _routerRoot points to the Vue instance
    let depth = 0
    let inactive = false
    while(parent && parent._routerRoot ! == parent) {if (parent.$vnode && parent.$vnode.data.routerView) {
        depth++
      }
      // Process the keep-alive component
      if (parent._inactive) {
        inactive = true
      }
      parent = parent.$parent
    }
    data.routerViewDepth = depth

    // render previous view if the tree is inactive and kept-alive
    // Render the cached keep-alive component
    if (inactive) {
      return h(cache[name], data, children)
    }
    const matched = route.matched[depth]
    // render empty node if no matched route
    if(! matched) { cache[name] =null
      return h()
    }
    const component = cache[name] = matched.components[name]
    // attach instance registration hook
    // this will be called in the instance's injected lifecycle hooks
    // Add registration hooks, which are injected into the component's lifecycle hooks
    // In SRC /install.js, this is called in the beforeCreate hook
    data.registerRouteInstance = (vm, val) = > {
      // val could be undefined for unregistration
      const current = matched.instances[name]
      if( (val && current ! == vm) || (! val && current === vm) ) { matched.instances[name] = val } }// also register instance in prepatch hook
    // in case the same component instance is reused across different routes
    ;(data.hook || (data.hook = {})).prepatch = (_, vnode) = > {
      matched.instances[name] = vnode.componentInstance
    }
    // resolve props
    let propsToPass = data.props = resolveProps(route, matched.props && matched.props[name])
    if (propsToPass) {
      // clone to prevent mutation
      propsToPass = data.props = extend({}, propsToPass)
      // pass non-declared props as attrs
      const attrs = data.attrs = data.attrs || {}
      for (const key in propsToPass) {
        if(! component.props || ! (keyin component.props)) {
          attrs[key] = propsToPass[key]
          delete propsToPass[key]
        }
      }
    }

    return h(component, data, children)
  }
}

function resolveProps (route, config) {
  switch (typeof config) {
    case 'undefined':
      return
    case 'object':
      return config
    case 'function':
      return config(route)
    case 'boolean':
      return config ? route.params : undefined
    default:
      if(process.env.NODE_ENV ! = ='production') {
        warn(
          false.`props in "${route.path}" is a The ${typeof config}, ` +
          `expecting an object, function or boolean.`)}}}function extend (to, from) {
  for (const key in from) {
    to[key] = from[key]
  }
  return to
}
Copy the code

Then there is the RouterLink component:

/ SRC /components/link.js

/* @flow */

import { createRoute, isSameRoute, isIncludedRoute } from '.. /util/route'
import { _Vue } from '.. /install'

// work around weird flow bug
const toTypes: Array<Function> = [String.Object]
const eventTypes: Array<Function> = [String.Array]

export default {
  name: 'RouterLink'.props: {
    to: {
      type: toTypes,
      required: true
    },
    tag: {
      type: String.default: 'a'
    },
    exact: Boolean.append: Boolean.replace: Boolean.activeClass: String.exactActiveClass: String.event: {
      type: eventTypes,
      default: 'click'
    }
  },
  render (h: Function) {
    // Get the mounted instance of VueRouter
    const router = this.$router
    // Get the current routing object
    const current = this.$route
    // Obtain the current matched routing information
    const { location, route, href } = router.resolve(this.to, current, this.append)

    const classes = {}
    const globalActiveClass = router.options.linkActiveClass
    const globalExactActiveClass = router.options.linkExactActiveClass
    // Support global empty active class
    const activeClassFallback = globalActiveClass == null
      ? 'router-link-active'
      : globalActiveClass
    const exactActiveClassFallback = globalExactActiveClass == null
      ? 'router-link-exact-active'
      : globalExactActiveClass
    const activeClass = this.activeClass == null
      ? activeClassFallback
      : this.activeClass
    const exactActiveClass = this.exactActiveClass == null
      ? exactActiveClassFallback
      : this.exactActiveClass
    const compareTarget = location.path
      ? createRoute(null, location, null, router)
      : route

    classes[exactActiveClass] = isSameRoute(current, compareTarget)
    classes[activeClass] = this.exact
      ? classes[exactActiveClass]
      : isIncludedRoute(current, compareTarget)

    const handler = e= > {
      if (guardEvent(e)) {
        if (this.replace) {
          router.replace(location)
        } else {
          router.push(location)
        }
      }
    }
    
    // Event binding
    const on = { click: guardEvent }
    if (Array.isArray(this.event)) {
      this.event.forEach(e= > { on[e] = handler })
    } else {
      on[this.event] = handler
    }

    const data: any = {
      class: classes
    }

    if (this.tag === 'a') {
      data.on = on
      data.attrs = { href }
    } else {
      // find the first <a> child and apply listener and href
      // Find the first  to give this element the event binding and href attribute
      const a = findAnchor(this.$slots.default)
      if (a) {
        // in case the <a> is a static node
        a.isStatic = false
        const extend = _Vue.util.extend
        const aData = a.data = extend({}, a.data)
        aData.on = on
        const aAttrs = a.data.attrs = extend({}, a.data.attrs)
        aAttrs.href = href
      } else {
        // doesn't have <a> child, apply listener to self
        // Bind events to the current element without 
        data.on = on
      }
    }

    return h(this.tag, data, this.$slots.default)
  }
}

function guardEvent (e) {
  // don't redirect with control keys
  if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) return
  // don't redirect when preventDefault called
  if (e.defaultPrevented) return
  // don't redirect on right click
  if(e.button ! = =undefined&& e.button ! = =0) return
  // don't redirect if `target="_blank"`
  if (e.currentTarget && e.currentTarget.getAttribute) {
    const target = e.currentTarget.getAttribute('target')
    if (/\b_blank\b/i.test(target)) return
  }
  // this may be a Weex event which doesn't have this method
  if (e.preventDefault) {
    e.preventDefault()
  }
  return true
}

function findAnchor (children) {
  if (children) {
    let child
    for (let i = 0; i < children.length; i++) {
      child = children[i]
      if (child.tag === 'a') {
        return child
      }
      if (child.children && (child = findAnchor(child.children))) {
        return child
      }
    }
  }
}
Copy the code

conclusion

Here, vue-Router source code analysis will come to an end, although not line by line to understand the author’s ideas, but also as a whole to smooth the operation of the project principle, understand the principle is more convenient for our daily needs to develop. Finally, thank you for your enjoyment.