Historical review:

  • 【 VUE series 】 Gracefully generate dynamic forms with VUE (I)
  • 【 VUE series 】 Gracefully generate dynamic forms with VUE (2)
  • Vue series: Vue-DevTools you don’t know
  • Vue series vUE – Router source code analysis
  • [VUE series] In-depth understanding of VUex
  • 【 VUE series 】 VUE and EChats combine spring, vUE plug-in Vechart
  • [VUE series] The VUE 2.0 project configures ESLint
  • [VUE series] Vue2.0 project configures Mocha unit tests
  • [VUE series] Two pictures, figure out the diff algorithm, really sweet ah!
  • [VUE series] Lazy loading of vue-Router routes occurs when element-UI is introduced on demand
  • Vue series the correct way to encapsulate common popover components
  • [VUE series] Develop components, package into VUE plug-ins, write documents, configure GH-Pages branch demo, release NPM package one stream

This is a collection from how to view vue-Router source code (V3.1.3), to vue-Router source code analysis, as well as the expansion of the relevant knowledge points involved, popular science a complete navigation analysis flow chart, a read not finished, recommended collection.

How to view vue-Router source code

There are many ways to view the source code, the following is my own read vue-router source code of the two methods, we are how to view the source code, welcome to leave a message in the comment area.

Check the vue-router source code.
  1. Download goodvue-routerSource code, install dependencies.
  2. findbuild/config.jsModify themodule.exports, only keepesOther notes.
module.exports = [
    {
        file: resolve('dist/vue-router.esm.js'),
        format: 'es'
    }
].map(genConfig)
Copy the code
  1. Create one in the root directoryauto-running.jsFile, a script that listens for changes to the SRC filevue-routerSource code changes are performed by rebuilding vue-Router from scratchnode auto-running.jsCommand. The code for auto-running.js is as follows:
const { exec } = require('child_process')
const fs = require('fs')

let building = false

fs.watch('./src', {
  recursive: true
}, (event, filename) => {
  if (building) {
    return
  } else {
    building = true
    console.log('start: building ... ')
    exec('npm run build', (err, stdout, stderr) => {
      if (err) {
        console.log(err)
      } else {
        console.log('end: building: ', stdout)
      }
      building = false})}})Copy the code

4. Run the NPM run dev command to start vue-router

Check the vue-router source code.

General project node_modules vuE-router SRC is not all convenient to view the source code;

Dist > Vue-router.esm. js on node_modules of vue-router (dist> vue-router-modules).

Js file and not vue-router.js; This is because WebPack uses esm.js files for packaging.

Why type debugger in esm.js

In the dist/ directory of vue-Router source code, there are many different builds.

version UMD Common JS ES Module(based on build tool use) ES Modules(direct to browsers)
The full version vue-router.js vue-router.common.js vue-router.esm.js vue-router.esm.browser.js
Full version (production environment) vue-router.min.js vue-router.esm.browser.min.js
  • Full version: Contains both compiler and runtime versions
  • UMD: The UMD version can pass<script>The tag is used directly in the browser.
  • CommonJS: The CommonJS version works with older packaging tools such as WebPack1.
  • ES Module: There are two ES Modules buildfiles:
    1. Esms for packaging tools. Esms are designed to be analyzed statically, and packaging tools can take advantage of this to “tree-shaking”.
    2. ESM for browsers, passed in modern browsers<script type="module">Direct import

Now it’s clear why you should dot in the ESm.js file, because the ESM file is the ESM provided by the packaging tool, which can be “tree-shaking”.

Vue-router Indicates the directory tree of the project SRC

. ├ ─ ─ components │ ├ ─ ─ the link. The js │ └ ─ ─ the js ├ ─ ─ the create - the matcher. Js ├ ─ ─ the create - the route - map. Js ├ ─ ─history│ ├ ─ ─ the abstract. Js │ ├ ─ ─ base. Js │ ├ ─ ─ errors. Js │ ├ ─ ─ hash. Js │ └ ─ ─ HTML 5. Js ├ ─ ─ index. The js ├ ─ ─ the js └ ─ ─ util ├ ─ ─ Async. Js ├ ─ ─ dom. Js ├ ─ ─ the location. The js ├ ─ ─ the misc. Js ├ ─ ─ params. Js ├ ─ ─ path. The js ├ ─ ─ a push - state. Js ├ ─ ─ query. Js ├ ─ ─ Resolve - components. Js ├ ─ ─ the route. The js ├ ─ ─ scroll. Js ├ ─ ─ the state - key. Js └ ─ ─ a warn. JsCopy the code

Vue – the use of the router

Vue-router is a plug-in of VUE. It can be used in the same way as common vUE plug-ins. Examples /basic are used in the VUe-Router project. Note the code comments.

// 0. Import Vue and VueRouter for use in modular projects
import Vue from 'vue'
import VueRouter from 'vue-router'


// 1. To use the plug-in, the route must be explicitly installed via vue.use ()

       
        
        
       
      
$router and $route are injected globally.
$router routing instance, $route current routing object can be used in all vUE components instantiated
Vue.use(VueRouter)

// 2. Define the routing component
const Home = { template: '<div>home</div>' }
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
const Unicode = { template: '<div>unicode</div>' }

The instance receives an object parameter,
// Parameter mode: routing mode,
// routes The route configuration maps components to routes
const router = new VueRouter({
  mode: 'history'.routes: [{path: '/'.component: Home },
    { path: '/foo'.component: Foo },
    { path: '/bar'.component: Bar },
    { path: '/ e'.component: Unicode }
  ]
})

// 4. Create and mount root instances
// The router parameter is injected into the vUE so that the entire application has routing parameters
// Use the 
      
        component to switch routes
      
// There are special uses in template which we will discuss later
new Vue({
  router,
  data: (a)= > ({ n: 0 }),
  template: ` <div id="app"> <h1>Basic</h1> <ul> <! Router-link to="/">/</router-link></li> <li><router-link to="/foo">/foo</router-link></li> <li><router-link to="/bar">/bar</router-link></li> <! <router-link tag="li" to="/bar" :event="['mousedown', 'touchstart'] "> < a > / bar < / a > < / router - the link > < li > < the router - link to ="/e "> / e < / router - the link > < / li > < li > < the router - link to ="/e? T = % 25 n "> / e? T = % n < / router - the link > < / li > < li > < the router - link to = "/ e# % 25 n" > / e# % 25 n < / router - the link > < / li > <! -- Router-link can be used as a slot to insert content. <router-link to="/foo" v-slot="props"> <li :class="[props. IsActive && 'active', props.isExactActive && 'exact-active']"> <a :href="props.href" @click="props.navigate">{{ props.route.path }} (with v-slot).</a> </li> </router-link> </ul> <button id="navigate-btn" @click="navigateAndIncrement">On Success</button> <pre  id="counter">{{ n }}</pre> <pre id="query-t">{{ $route.query.t }}</pre> <pre id="hash">{{ $route.hash }}</pre> <! Router-view class="view"></router-view> </div> '.methods: {
    navigateAndIncrement () {
      const increment = (a)= > this.n++
      // After routing is registered, we can access the routing instance from within the Vue instance via this.$router.
      // Access the current route through this.$route
      if (this.$route.path === '/') {
        // this.$router.push adds a new record to the history stack
        // Router-link 
      
        also calls router-push
      
        this.$router.push('/foo', increment)
      } else {
        this.$router.push('/', increment)
      }
    }
  }
}).$mount('#app')
Copy the code

The reason for using this.$router is that you don’t want the user to import a route in every component that needs to encapsulate a route independently.

is the top-level exit that renders components matching the top-level routes. To render components in nested exits, use the children configuration in the VueRouter parameter.

Route injection and route instantiation do something

Vue provides a plug-in registration mechanism. Each plug-in needs to implement a static install method. When a plug-in is registered with vue. use, install is executed, and the first argument to this method is a Vue object.

Why is the first argument to Install’s plug-in method Vue

The policy of the Vue plug-in, when writing the plug-in does not need the inport Vue, when registering the plug-in, the plug-in is forced to insert a parameter is the Vue instance.

installWhy is itstaticmethods

Static methods of a class are defined with the static keyword. Static methods cannot be called on an instance of the class, only from the class itself. Install here can only be called by the vue-Router class, not its instance (in case instances of vue-Router are called externally).

vue-routerWhat was installed at injection time
// Introduce the install method
import { install } from './install'

export default class VueRouter {
    Define the install static method in the VueRouter class
    static install: (a)= > void;
}

// Copy VueRouter. Install
VueRouter.install = install

// Add a link to vue-router to register vue-router directly
if (inBrowser && window.Vue) {
  window.Vue.use(VueRouter)
}
Copy the code

In the vue-Router source code, the entry file is SRC /index.js, where the VueRouter class is defined. In the VueRouter class, the static method install is defined in SRC /install.js.

What was installed during route registration in the SRC /install.js file
import View from './components/view'
import Link from './components/link'

// Export the Vue instance
export let _Vue

Use (vueRouter) is equivalent to vue.use (vueRouter. Install ()).
export function install (Vue) {
  // Vue-router registers only once
  if (install.installed && _Vue === Vue) return
  install.installed = true

  // Save the Vue instance for other plug-in files
  _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)
    }
  }

  /** * To register vue-router, mix two life cycle beforeCreate and destroyed * in beforeCreated to initialize vue-router and _route response */ for all vUE components
  Vue.mixin({
    beforeCreate () {
      // Attach the vue instance to the _routerRoot of the vue instance if the custom attribute of the vue instance has a router
      if (isDef(this.$options.router)) {
        // Pass the cat to the boss
        this._routerRoot = this

        // Mount the VueRouter instance to _router
        this._router = this.$options.router

        // Initialize vue-router, init is the core method, init is defined in SRC /index.js, see later
        this._router.init(this)

        // Implicitly attach the current route object to the current component's data, making it a responsive variable.
        Vue.util.defineReactive(this.'_route'.this._router.history.current)
      } else {
        // Find the parent component's _routerRoot, which has no _routerRoot
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
      }
      registerInstance(this.this)
    },
    destroyed () {
      registerInstance(this)}})$router = router instance * $route = current route */
  Object.defineProperty(Vue.prototype, '$router', {
    get () { return this._routerRoot._router }
  })

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

  /** * Inject two global components * 
      
        * 
       
         */
       
      
  Vue.component('RouterView', View)
  Vue.component('RouterLink', Link)

  /** * Vue. Config is an object that contains the global configuration of Vue
  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

To ensure that the VueRouter is executed only once, an identity installed is added when the install logic is executed. Save the Vue in a global variable to make it easy to use anywhere in the VueRouter plug-in. This is a good idea, so you can store a global _Vue when you write your own Vue plugin.

The core of the VueRouter installation is to mix beforeCreate and destroyed hook functions into all components of the application via mixin. In the beforeCreate hook function, we define the private attributes _routerRoot and _Router.

  • _routerRoot: Assigning a Vue instance to _routerRoot is equivalent to attaching the Vue and instance to the _routerRoot property of each component$parent._routerRootSo that all components can be owned_routerRootAlways pointing to the rootVueInstance.
  • _router:this.$options.routerSo that each VUE component can get an instance of the VueRouter

Use Vue’s defineReactive method to make _route a reactive object. This._router.init () initializes the router, and init methods are important in SRC /index.js. RegisterInstance is also covered later.

This.$router and this.$route can be accessed on all component instances of the application. This.$router. Replace: this.$router. Are two very useful methods.

The Vue.component method defines two global

and

components.

is similar to the A tag.

is the route exit. In

, the route is switched to render different Vue components.




Finally, the merging strategy of route guard is defined and the merging strategy of Vue is adopted.

summary

The Vue plug-in needs to provide the install method for plug-in injection. When VueRouter is installed, it will inject beforeCreate and DeStoryed hook functions into all application components. The beforeCreate defines some private variables to initialize the route. Two components and two apis are registered globally.

So the question is, what does initializing the route do

The VueRouter class defines a lot of properties and methods, so let’s start with the initialization route method init. The code to initialize the route is this._router.init(this), init receives the Vue instance, which is the app below. The notes are very detailed, so I’m not going to write them here.

init (app: any /* Vue component instance */) {
    // vueRouter may instantiate multiple apps to hold multiple instances of vueRouter
    this.apps.push(app)

    // Ensure that the VueRouter is initialized only once, and if it is, the subsequent logic is terminated
    if (this.app) {
      return
    }

    // Mount vue instance to vueRouter, router instance to vue instance
    this.app = app

    // History is an important global variable maintained by vueRouter
    const history = this.history

    TransitionTo is the core method of History, more on that later
    if (history instanceof HTML5History) {
      history.transitionTo(history.getCurrentLocation())
    } else if (history instanceof HashHistory) {
      const setupHashListener = (a)= > {
        history.setupListeners()
      }
      history.transitionTo(
        history.getCurrentLocation(),
        setupHashListener,
        setupHashListener
      )
    }

    // Route global listener to maintain the current route
    // Since _route is defined as a responsive attribute when install executes,
    // When route changes _route updates, subsequent view updates render depending on _route
    history.listen(route= > {
      this.apps.forEach((app) = > {
        app._route = route
      })
    })
  }
Copy the code

Now look at what constructor does when new VueRouter.

constructor (options: RouterOptions = {}) {
    this.app = null
    this.apps = []
    this.options = options
    this.beforeHooks = []
    this.resolveHooks = []
    this.afterHooks = []
    CreateMatcher returns an object {match, addRoutes}
    this.matcher = createMatcher(options.routes || [], this)

    // The default hash mode
    let mode = options.mode || 'hash'

    // H5 history is compatible
    this.fallback = mode === 'history'&&! supportsPushState && options.fallback ! = =false
    if (this.fallback) {
      mode = 'hash'
    }
    if(! inBrowser) { mode ='abstract'
    }
    this.mode = mode

    // This. History is an instance of History
    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

{routes, mode: ‘history’}}. Routes is mandatory. Mode defaults to hash mode. What else did vueRouter define?

...

match (
    raw: RawLocation,
    current?: Route,
    redirectedFrom?: Location
  ): Route {
    return this.matcher.match(raw, current, redirectedFrom)
}

// Get the current routeget currentRoute (): ? Route {return this.history && this.history.current
}
  
init(options) { ... }

beforeEach(fn) { ... }
beforeResolve(fn) { ... }
afterEach(fn) { ... }
onReady(cb) { ... }

push(location) { ... }
replace(location) { ... }
back() { ... }
go(n) { ... }
forward() { ... }

// Get the matched routing componentgetMatchedComponents (to? : RawLocation | Route):Array<any> {
    const route: any = to
      ? to.matched
        ? to
        : this.resolve(to).route
      : this.currentRoute
    if(! route) {return[]}return [].concat.apply([], route.matched.map(m= > {
      return Object.keys(m.components).map(key= > {
        return m.components[key]
      })
    }))
}

addRoutes (routes: Array<RouteConfig>) {
    this.matcher.addRoutes(routes)
    if (this.history.current ! == START) {this.history.transitionTo(this.history.getCurrentLocation())
    }
}
Copy the code

At instantiation time, vueRouter defines apis like History: Push, replace, back, Go, forward, routing matchers, adding router dynamic update methods, and so on.

summary

Install starts with the init method, and then instantiates vueRouter to define some properties and methods. Init uses history.transitionto to transition routes. Matcher route matcher is the core function of route switching, route and component matching. So… en

Matcher takes a look

In the VueRouter object there is the following code:

CreateMatcher returns an object {match, addRoutes}
this.matcher = createMatcher(options.routes || [], this)... match ( raw: RawLocation, current? : Route, redirectedFrom? : Location ): Route {return this.matcher.match(raw, current, redirectedFrom)
}

...

const route = this.match(location, current)
Copy the code

We can observe that the route object is obtained by this.match(), which in turn is obtained by this.matcher. Match (), which is handled by the createMatcher function. Next, let’s look at the implementation of the createMatcher function.

createMatcher

The createMatcher related implementations are in SRC /create-matcher.js.

/** * create createMatcher * @param {*} routes * @param {*} router Route instance ** Return an object {* match, Match * addRoutes // Update route configuration *} */
export function createMatcher (routes: Array
       
        , router: VueRouter
       ) :Matcher {
  const { pathList, pathMap, nameMap } = createRouteMap(routes)

  function addRoutes (routes) {
    createRouteMap(routes, pathList, pathMap, nameMap)
  }

  function match (raw: RawLocation, currentRoute? : Route, redirectedFrom? : Location) :Route {... return { match, addRoutes } }Copy the code

CreateMatcher receives two parameters. Routes is the user-defined route configuration, and router is the instance returned by new VueRouter. Routes is an array that defines the route configuration. Processed by the createRouteMap function as pathList, pathMap, nameMap, it returns an object {match, addRoutes}. That is, matcher is an object that exposes the match and addRoutes methods.

We’ll look at what the pathList, pathMap, and nameMap are in a moment, and we’ll look at the implementation of createRouteMap later.

  • PathList: An array of routing paths that stores all paths
  • PathMap: mapping table of route paths and route records, representing the mapping between a Path and A RouteRecord
  • NameMap: mapping table of route names and route records, representing the mapping relationship between Name and RouteRecord
RouteRecord

So what does the routing record look like?

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

RouteRecord is an object that contains all the information about a route: the path, the route re, the array of components corresponding to the path, the component instance, the route name, and so on.

createRouteMap

The createRouteMap function is implemented in SRC /create-route-map:

/** ** @param {*} routes user route configuration * @param {*} oldPathList oldPathList * @param {*} oldPathMap oldPathMap * @param {*} Old oldNameMap nameMap * /
export function createRouteMap (routes: Array
       
        , oldPathList? : Array
        
         , oldPathMap? : Dictionary
         
          , oldNameMap? : Dictionary
          
         
        
       ) :{
  pathList: Array<string>,
  pathMap: Dictionary<RouteRecord>,
  nameMap: Dictionary<RouteRecord>
} {
  // pathList is used to control the priority of route matching
  const pathList: Array<string> = oldPathList || []
  // Path route mapping table
  const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null)
  // Route name Route mapping table
  const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null)

  routes.forEach(route= > {
    addRouteRecord(pathList, pathMap, nameMap, route)
  })

  // Make sure wildcard routes are always last
  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
  }
}
Copy the code

The createRouteMap function converts the user’s route matching into a route mapping table, which is used for route switching. Routes executes the addRouteRecord method to generate a record for each route, as shown above. Let’s look at how to generate a record.

addRouteRecord
function addRouteRecord (pathList: Array
       
        , pathMap: Dictionary
        
         , nameMap: Dictionary
         
          , route: RouteConfig, parent? : RouteRecord, matchAs? : string
         
        
       ) {

  / /...
  // Create a route record
  const record: RouteRecord = { ... }

  // If the route records the nested route, loop through the parse nested route
  if (route.children) {
    // ...
    // Deep traverse recursively, passing the current record as parent
    route.children.forEach(child= > {
      const childMatchAs = matchAs
        ? cleanPath(`${matchAs}/${child.path}`)
        : undefined
      addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)
    })
  }

  // If there are multiple paths of the same type, only the first one will work, and subsequent ones will be ignored
  // Record the parsed route and add a record for pathList and pathMap
  if(! pathMap[record.path]) { pathList.push(record.path) pathMap[record.path] = record }// ...
}
Copy the code

The addRouteRecord function creates a route record object. If the current route record has nested patterns, the system iterates to create a route record and maps the route record according to the path and route name. All routing records are logged. The entire RouteRecord is a tree structure, where parent represents the parent RouteRecord.

if (name) {
  if(! nameMap[name]) { nameMap[name] = record }// ...
}
Copy the code

If we set name in the routing configuration, a record will be added to nameMap. After the createRouteMap method is executed, we can obtain the complete record of the route and obtain the route mapping corresponding to path and name. You can use path and name to quickly query the corresponding RouteRecord in pathMap and nameMap.

export function createMatcher (routes: Array
       
        , router: VueRouter
       ) :Matcher {
  / /...
  return {
    match,
    addRoutes
  }
}
Copy the code

Remember that createMatcher returns a match, so let’s look at the implementation of match.

match
/** ** @param {*} raw is RawLocation. The RawLocation type is a url string or RawLocation object. @param {*} currentRoute specifies the currentRoute. RedirectedFrom redirects (not important, negligible) */
function match (raw: RawLocation, currentRoute? : Route, redirectedFrom? : Location) :Route {

    // Location is an object similar to
    // {"_normalized":true,"path":"/","query":{},"hash":""}
    const location = normalizeLocation(raw, currentRoute, false, router)
    const { name } = location

    // nameMap mapping is performed if there is a route name
    Route params returns something that _createRoute handles
    if (name) {
      const record = nameMap[name]
      if(process.env.NODE_ENV ! = ='production') {
        warn(record, `Route with name '${name}' does not exist`)}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]
          }
        }
      }

      location.path = fillParams(record.path, location.params, `named route "${name}"`)
      return _createRoute(record, location, redirectedFrom)
    
    // If the route is configured with path, route records will be matched in pathList and PathMap
    // If matchRoute matches, _createRoute is returned
    } else if (location.path) {
      location.params = {}
      for (let i = 0; i < pathList.length; i++) {
        const path = pathList[i]
        const record = pathMap[path]
        if (matchRoute(record.regex, location.path, location.params)) {
          return _createRoute(record, location, redirectedFrom)
        }
      }
    }
    // Return something via _createRoute
    return _createRoute(null, location)
}
Copy the code

The match method receives paths, pre-routes, and redirects based on incoming raw and currentRoute, and returns _createRoute(). Let’s see what _createRoute returns to see what match returns.

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

The _createRoute function performs different processing according to whether route redirection and route renaming exist. The redirect and alias functions call _createRoute at the end, and createRoute at the end. It comes from util/route.

/** ** @param {*} Record is generally null * @param {*} location route object * @param {*} redirectedFrom redirect * @param {*} router * / vueRouter instanceexport functioncreateRoute ( record: ? RouteRecord, location: Location, redirectedFrom? :? Location, router? : VueRouter ): Route { const stringifyQuery = router && router.options.stringifyQuerylet query: any = location.query || {}
  try {
    query = clone(query)
  } catch (e) {}

  const route: Route = {
    name: location.name || (record && record.name),
    meta: (record && record.meta) || {},
    path: location.path || '/'.hash: location.hash || ' ',
    query,
    params: location.params || {},
    fullPath: getFullPath(location, stringifyQuery),
    matched: record ? formatMatch(record) : []
  }
  if(redirectedFrom) {route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery)} // Freeze the route once createdreturn Object.freeze(route)
}
Copy the code

CreateRoute can be created from record and location to eventually return a Route object, and cannot be modified externally, only accessed. One of the most important attributes in the Route object is matched, which is evaluated by formatMatch(Record) :

function formatMatch (record: ? RouteRecord) :Array<RouteRecord> {
  const res = []
  while (record) {
    res.unshift(record)
    record = record.parent
  }
  return res
}
Copy the code

Through the record cycle to find the parent, until the outermost layer is found, and all the records are pushed into an array, finally a record array, this matched provides an important role for the following rendering components.

summary

The main process of matcher is to return an object {match, addRoutes} through createMatcher. AddRoutes is used to dynamically addRoutes. This routing object records the basic information of the current route and the routing record of path matching, which provides the basis for path switching and component rendering. How do you switch paths, how do you render components. Somebody have a drink? Let’s move on.

Path switching

Remember that the vue-router is initialized with init (history.transitionTo), which is used to initialize routes for different routing modes. Bottom layers, including history.push and history.replace, call it. It’s the route switch, and it’s very important. Its implementation is in SRC /history/base.js, so let’s take a look.

transitionTo ( location: RawLocation, onComplete? :Function, onAbort? :Function
) {
    // Call match to get a matching route object
    const route = this.router.match(location, this.current)
    
    // Transition processing
    this.confirmTransition(
        route,
        () => {
            // Update the current route object
            this.updateRoute(route)
            onComplete && onComplete(route)
            
            // Update hash mode of URL address Update hash value History mode is updated using pushState/replaceState
            this.ensureURL()
    
            // fire ready cbs once
            if (!this.ready) {
                this.ready = true
                this.readyCbs.forEach(cb= > {
                cb(route)
                })
            }
        },
        err => {
            if (onAbort) {
                onAbort(err)
            }
            if (err && !this.ready) {
                this.ready = true
                this.readyErrorCbs.forEach(cb= > {
                cb(err)
                })
            }
        }
    )
}
Copy the code

TransitionTo can accept location, onComplete, and onAbort, which are callbacks to the target path, successful path switchover, and failed path switchover. The transitionTo function does two things: it first matches the destination route object based on the destination location and the current route object using the this.router.match method. A route looks like this:

Const route = {fullPath: "/detail/394" hash: "" matched: [{...}] meta: {title: "detail"} name: "detail" params: {id: "394"} path: "/detail/394" query: {} }Copy the code

An object that contains basic information about the destination route. The confirmTransition method is then executed for the actual route switch. Because there are some asynchronous components, there are some asynchronous operations. Specific implementation:

confirmTransition (route: Route, onComplete: Function, onAbort? :Function) {
    const current = this.current
    const abort = err= > {
      // ...
      onAbort && onAbort(err)
    }
    
    // If the current route is the same as the previous route, ensure that the URL returns directly
    if (
      isSameRoute(route, current) &&
      route.matched.length === current.matched.length
    ) {
      this.ensureURL()
      return abort(new NavigationDuplicated(route))
    }

    // Cross-compare the current route record with the current route record through an asynchronous queue
    // In order to accurately get parent-child routing updates, we can know exactly which components need to be updated and which do not need to be updated
    const { updated, deactivated, activated } = resolveQueue(
      this.current.matched,
      route.matched
    )

    // Execute the response tick function in the asynchronous queue
    // The corresponding routing hook functions are stored in the queue array
    const queue: Array<? NavigationGuard> = [].concat(// Leave tick
      extractLeaveGuards(deactivated),
      // Global before check
      this.router.beforeHooks,
      // in-component update hooks
      extractUpdateHooks(updated),
      // The beforeEnter hook of the route to be updated
      activated.map(m= > m.beforeEnter),
      // Asynchronous components
      resolveAsyncComponents(activated)
    )

    this.pending = route

    // Queue execution iterator function
    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
            // If there is a navigation hook, you need to call next(), otherwise the callback will not execute and the navigation will not continue
            next(to)
          }
        })
      } catch (e) {
        abort(e)
      }
    }

    RunQueue initiates the execution of the asynchronous function queue in a recursive callback manner
    runQueue(queue, iterator, () => {
      const postEnterCbs = []
      const isValid = (a)= > this.current === route

      // The hook inside the component
      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 we need to wait for asynchronous components and whether it is OK
      runQueue(queue, iterator, () => {
        // Ensure that the period is still the current route
        if (this.pending ! == route) {return abort()
        }
        this.pending = null
        onComplete(route)
        if (this.router.app) {
          this.router.app.$nextTick((a)= > {
            postEnterCbs.forEach(cb= > {
              cb()
            })
          })
        }
      })
    })
}
Copy the code

Check whether the target route is the same as current and call this.ensureUrl and abort if they are the same.

// ensureUrl todo

Next we execute the resolveQueue function, which we should take a closer look at:

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++) {
    if(current[i] ! == next[i]) {break}}return {
    updated: next.slice(0, i),
    activated: next.slice(i),
    deactivated: current.slice(i)
  }
}
Copy the code

The resolveQueue function takes two arguments: the current route’s matched and the destination route’s matched, which is an array. By traversing the array of route records that are compared twice, the position is recorded when one of the route records is different, and the traversal is terminated. For next, the value from 0 to I and current are the same. The value from PORT I is different. Next is activated after I, current is deactivated after I, and the same value is updated. After being processed by the resolveQueue, the part of the route that needs to be changed is obtained. You can then execute a series of hook functions based on the route changes. The complete navigation parsing process has 12 steps, followed by an internal implementation of vue-Router route switching. Look forward!

Routing Changes How are routing components rendered

After a route change, the route component is rendered in the

component, which is defined in SRC/Components /view.js.

The router – the view components
export default {
  name: 'RouterView'.functional: true.props: {
    name: {
      type: String.default: 'default'
    }
  },
  render (_, { props, children, parent, data }) {
    data.routerView = true
    const h = parent.$createElement
    const name = props.name
    const route = parent.$route
    const cache = parent._routerViewCache || (parent._routerViewCache = {})
    let depth = 0
    let inactive = false
    while(parent && parent._routerRoot ! == parent) {if (parent.$vnode && parent.$vnode.data.routerView) {
        depth++
      }
      if (parent._inactive) {
        inactive = true
      }
      parent = parent.$parent
    }
    data.routerViewDepth = depth
    if (inactive) {
      return h(cache[name], data, children)
    }
    const matched = route.matched[depth]
    if(! matched) { cache[name] =null
      return h()
    }
    const component = cache[name] = matched.components[name]
    data.registerRouteInstance = (vm, val) = > {     
      const current = matched.instances[name]
      if( (val && current ! == vm) || (! val && current === vm) ) { matched.instances[name] = val } } ;(data.hook || (data.hook = {})).prepatch = (_, vnode) = > {
      matched.instances[name] = vnode.componentInstance
    }
    let propsToPass = data.props = resolveProps(route, matched.props && matched.props[name])
    if (propsToPass) {
      propsToPass = data.props = extend({}, propsToPass)
      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)
  }
}
Copy the code



is a render function that uses the render function of Vue. It takes two parameters, the first is a Vue instance, the second is a context, and we can get props, children, parent, and data through object parsing. Used to create

.


The router – link component

It can be used by users in components with routing function. The target address can be specified by using the TO attribute. By default, it is rendered as tag.

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) {
    const router = this.$router
    const current = this.$route
    const { location, route, href } = router.resolve(this.to, current, this.append)
    const classes = {}
    const globalActiveClass = router.options.linkActiveClass
    const globalExactActiveClass = router.options.linkExactActiveClass
    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)
        }
      }
    }
    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 {
      const a = findAnchor(this.$slots.default)
      if (a) {
        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 {
        data.on = on
      }
    }
    return h(this.tag, data, this.$slots.default)
  }
}
Copy the code


features:

  • historyPatterns andhashMode label consistent against unsupportedhistoryIs automatically downgraded tohashMode.
  • Can be route guard, do not reload the page

The

implementation is also based on the render function. Internal implementations are also implemented through history.push() and history.replace().

Path variation is the most important function of routing: routing always maintains the current route; When you want to switch, the current line will be switched to the target line. During the switching process, a series of navigation guard hook functions will be executed, the URL will be changed, and the corresponding components will be rendered. After the switching, the target line will be updated and replaced with the current line, which will be used as the basis for the next path switching.

Knowledge supplement

Difference between Hash mode and History mode

Vue – Router uses hash mode by default. When the HASH mode is used, the URL is changed and the page is not reloaded. This mode has been used in Internet Explorer 6 and is a stable routing mode. But the HASH URL had a hash, which looked ugly, and then when HTML5 came out, it had history mode.

The history mode uses history.pushState to jump to urls without reloading the page. But older browsers don’t work with history mode, so sometimes we have to use hash mode for backwards compatibility.

In history mode, 404 is returned if a page is visited that does not exist. To solve this problem, you need to configure the background to return an index.html page when the URL does not match any static resources. Or add a unified configuration error page to the routing configuration.

Why does history have this problem, but not hash?

In hash mode, only the content before the hash symbol is included in the request, such as www.abc.com. Therefore, the back end will not return a 404 error even if the route is not fully covered

In history mode, the URL of the front end must be the same as the URL of the back end, for example, www.abc.com/book/id. If after… /book/id route processing, will return a 404 error.

const router = new VueRouter({
    mode: 'history'.routes: [{path: The '*'.component: NotFoundComponent
        }
    ]
})
Copy the code

The Vue RouterqueryparamsThe use and difference between

There are two concepts in Vue-Router: Query and Params. At first I didn’t know the difference between them, and I’m sure some of you did. Here’s a summary for easy memorization and understanding.

  • The use of the query
// With query parameters, change to /register? plan=private
router.push({ path: 'register'.query: {plan: 'private'}})
Copy the code
  • Configuration and invocation of params
  • Route configuration, use params to pass parameters, use name
{
    path: '/detail/:id'.name: 'detail'.component: Detail,
}
Copy the code
  • callthis.$router.pushTo pass params parameters, use name. The name must be set in the route configuration.
this.$router.push({
    name: 'detail'.params: {
        id: '2019'}})Copy the code
  • Params receives parameters
const { id } = this.$route.params
Copy the code

Query is usually used with PATH. Query Takes the query parameter, params path parameter. If path is provided, params is ignored.

// Params is not valid
router.push({ path: '/user'.params: { userId }}) // -> /user
Copy the code

Navigation guard

Navigation indicates that the route is changing. The navigation guard provided by vue-Router is mainly used to guard the navigation by jumping or canceling. There are three types of navigational guards: global guards, individual route guards, and intra-component guards.

Global guard:

  • Global front-guard beforeEach (to, from, next)
  • BeforeResolve (to, from, next)
  • AfterEach (to, from)

Single route guard:

  • Route Front-guard beforeEnter (to, from, Next)

Guards within components:

  • BeforeRouterEnter (to, from, next) Next can be a function because the guard cannot get the component instance and the new component has not yet been created
  • Route changes, called when the component is reused (to, from, next)
  • Call beforeRouteLeave when navigating away from the component’s route
Complete navigation parsing flowchart

  1. Navigation triggered
  2. Call the leave guard in the inactivated componentbeforeRouteLeave
  3. Call globalbeforeEachThe guards
  4. Called in a reused componentbeforeRouteUpdateGuard (2.2+)
  5. Called in the routing configurationbeforeEnter
  6. Parse the asynchronous routing component
  7. Called in the active componentbeforeRouteEnter
  8. Call globalbeforeResolveThe guards
  9. Navigation confirmed
  10. Call globalafterEachhook
  11. Triggering DOM updates
  12. Call with the created instancebeforeRouterEnterThe callback function passed to Next in the guard