This is the 8th day of my participation in the genwen Challenge

This article will continue with the vue-Router4 analysis of the routers attributes and the installation of the VueRouter instance

Routes properties

Back in the createRouter method, you can see that options.routes is used in only one place in the method. It serves as the createRouterMatcher parameter and returns an object of type RouterMatcher

export function createRouter(options: RouterOptions) :Router {
  const matcher = createRouterMatcher(options.routes, options)
  / /...
}
Copy the code

The entry to the matcher module is SRC /matcher/index.ts. This module provides routing configuration related properties and methods. The matcher interface is defined as follows

// src/matcher/index.ts
interface RouterMatcher {
  addRoute: (record: RouteRecordRaw, parent? : RouteRecordMatcher) = > () = > void
  removeRoute: {
    (matcher: RouteRecordMatcher): void
    (name: RouteRecordName): void
  }
  getRoutes: () = > RouteRecordMatcher[]
  getRecordMatcher: (name: RouteRecordName) = > RouteRecordMatcher | undefined
  resolve: (location: MatcherLocationRaw, currentLocation: MatcherLocation) = > MatcherLocation
}
Copy the code

The basic logic of the createRouterMatcher function is simplified as follows

function createRouterMatcher(routes: RouteRecordRaw[], globalOptions: PathParserOptions) :RouterMatcher {
  const matchers: RouteRecordMatcher[] = []
  const matcherMap = new Map<RouteRecordName, RouteRecordMatcher>()
  globalOptions = mergeOptions(
    { strict: false.end: true.sensitive: false } as PathParserOptions,
    globalOptions
  )

  function getRecordMatcher(name: RouteRecordName) {
    // ...
  }

  function addRoute(record: RouteRecordRaw, parent? : RouteRecordMatcher, originalRecord? : RouteRecordMatcher) {
    // ...
  }

  function removeRoute(matcherRef: RouteRecordName | RouteRecordMatcher) {
    // ...
  }

  function getRoutes() {
    // ...
  }

  function insertMatcher(matcher: RouteRecordMatcher) {
    // ...
  }

  function resolve(location: Readonly
       
        , currentLocation: Readonly
        
       ) :MatcherLocation {
    // ...
  }

  // add initial routes
  routes.forEach(route= > addRoute(route))

  return { addRoute, resolve, removeRoute, getRoutes, getRecordMatcher }
}
Copy the code

This function takes two arguments, the first being the route configuration array and the second being the options passed in when VueRouter was initialized. We then declare two variables matchers and matcherMap. We then declare a series of methods. Before returning, we traverse routes and convert the route configuration to matcher by using the addRoute method

Let’s look at each of these methods one by one

  • AddRoute method
function addRoute(record: RouteRecordRaw, parent? : RouteRecordMatcher, originalRecord? : RouteRecordMatcher) {
  letisRootAdd = ! originalRecordlet mainNormalizedRecord = normalizeRouteRecord(record)
  mainNormalizedRecord.aliasOf = originalRecord && originalRecord.record
  const options: PathParserOptions = mergeOptions(globalOptions, record)
  const normalizedRecords: typeof mainNormalizedRecord[] = [
    mainNormalizedRecord,
  ]
  if ('alias' in record) {
    const aliases =
      typeof record.alias === 'string' ? [record.alias] : record.alias!
    for (const alias of aliases) {
      normalizedRecords.push(
        assign({}, mainNormalizedRecord, {
          components: originalRecord
            ? originalRecord.record.components
            : mainNormalizedRecord.components,
          path: alias,
          aliasOf: originalRecord
            ? originalRecord.record
            : mainNormalizedRecord,
        }) as typeof mainNormalizedRecord
      )
    }
  }

  let matcher: RouteRecordMatcher
  let originalMatcher: RouteRecordMatcher | undefined

  for (const normalizedRecord of normalizedRecords) {
    let { path } = normalizedRecord
    if (parent && path[0]! = ='/') {
      let parentPath = parent.record.path
      let connectingSlash =
        parentPath[parentPath.length - 1= = ='/' ? ' ' : '/'
      normalizedRecord.path =
        parent.record.path + (path && connectingSlash + path)
    }
    matcher = createRouteRecordMatcher(normalizedRecord, parent, options)
    if (originalRecord) {
      originalRecord.alias.push(matcher)
    } else {
      originalMatcher = originalMatcher || matcher
      if(originalMatcher ! == matcher) originalMatcher.alias.push(matcher)if(isRootAdd && record.name && ! isAliasRecord(matcher)) removeRoute(record.name) }if ('children' in mainNormalizedRecord) {
      let children = mainNormalizedRecord.children
      for (let i = 0; i < children.length; i++) {
        addRoute(
          children[i],
          matcher,
          originalRecord && originalRecord.children[i]
        )
      }
    }
    originalRecord = originalRecord || matcher
    insertMatcher(matcher)
  }

  return originalMatcher
    ? () = > {
        removeRoute(originalMatcher!)
      }
    : noop
}

Copy the code

All this does is create a matcher object based on the route configuration object, add it to the Matchers array, and return a remove route method or noop based on the originalMatcher condition (let noop = () => {}). The route configuration transmitted in the application is incomplete. Therefore, you need to format the route configuration using the normalizeRouteRecord method to generate a complete route configuration object. The props properties are formatted using the normalizeRecordProps function. The formatting object is generated based on the Component or components of the route configuration object. If there is a Component attribute, the props object contains a default attribute and is assigned to the props in the configuration. Otherwise, the key of the Components object is used. And take the corresponding value from the route configuration property props

function normalizeRouteRecord(
  record: RouteRecordRaw
) :RouteRecordNormalized {
  return {
    path: record.path,
    redirect: record.redirect,
    name: record.name,
    meta: record.meta || {},
    aliasOf: undefined.beforeEnter: record.beforeEnter,
    props: normalizeRecordProps(record),
    children: record.children || [],
    instances: {},
    leaveGuards: new Set(),
    updateGuards: new Set(),
    enterCallbacks: {},
    components:
      'components' in record
        ? record.components || {}
        : { default: record.component! }}},function normalizeRecordProps(
  record: RouteRecordRaw
) :Record<string._RouteRecordProps> {
  const propsObject = {} as Record<string, _RouteRecordProps>
  const props = (record as any).props || false
  if ('component' in record) {
    propsObject.default = props
  } else {
    for (let name in record.components)
      propsObject[name] = typeof props === 'boolean' ? props : props[name]
  }

  return propsObject
}
Copy the code

After the normalizeRouteRecord method is called to format the route configuration object, the processed mainNormalizedRecord object is added to the normalizedRecords array

If there is an alias, add the record to the normalizedRecords array. The basic logic is to copy the mainNormalizedRecord and reset the components, path, aliasOf properties. In other words, Aliasing works by copying records and adjusting some properties to get a new record

The above code is a preparation for the creation of a matcher. Continue to analyze the code by first preparing two variables: matcher and originalMatcher, and then iterating through normalizedRecords

Inside the traversal, according to the route configuration object, create matcher, and insert into matchers, divided into the following steps:

  • 1. If a child route is configured and path does not start with a slash (/), add the path of the parent route and the path of the child route to generate the complete path

  • 2. Call createRouteRecordMatcher to create a matcher object. If parent exists, add the current matcher object to parent-children

function createRouteRecordMatcher(
  record: Readonly<RouteRecord>,
  parent: RouteRecordMatcher | undefined, options? : PathParserOptions) :RouteRecordMatcher {
  const parser = tokensToParser(tokenizePath(record.path), options)
  const matcher: RouteRecordMatcher = assign(parser, {
    record,
    parent,
    children: [].alias: [],})if (parent) {
    if(! matcher.record.aliasOf === ! parent.record.aliasOf) parent.children.push(matcher) }return matcher
}
Copy the code

The matcher object is of type RouteRecordMatcher, which inherits from the PathParser interface, so a matcher object should contain the following properties and methods, The first five properties or methods are created by tokensToParser(tokenizePath(Record.path), options). The implementation logic of these properties or methods will be analyzed in the method below

-re: RegExp

  • score: Array<number[]>
  • keys: PathParserParamKey[]
  • parse(path: string): PathParams | null
  • stringify(params: PathParams): string
  • Record: RouteRecord stores formatted route configuration records
  • Parent: RouteRecordMatcher | undefined parent route matcher object
  • Children: RouteRecordMatcher[] child route, initialized to an empty array
  • Alias: RouteRecordMatcher[] alias, initialized as an empty array

Before analyzing tokensToParser, we need to take a look at tokenizePath(Record.path), which converts path to a token array.

export const enum TokenType {
  Static,
  Param,
  Group,
}

const enum TokenizerState {
  Static,
  Param,
  ParamRegExp, // custom re for a param
  ParamRegExpEnd, // check if there is any ? + *
  EscapeNext,
}

interface TokenStatic {
  type: TokenType.Static
  value: string
}

interface TokenParam {
  type: TokenType.Param regexp? : stringvalue: string
  optional: boolean
  repeatable: boolean
}

interface TokenGroup {
  type: TokenType.Group
  value: Exclude<Token, TokenGroup>[]
}

export type Token = TokenStatic | TokenParam | TokenGroup

const ROOT_TOKEN: Token = {
  type: TokenType.Static,
  value: ' ',}const VALID_PARAM_RE = /[a-zA-Z0-9_]/
// After some profiling, the cache seems to be unnecessary because tokenizePath
// (the slowest part of adding a route) is very fast

// const tokenCache = new Map<string, Token[][]>()

export function tokenizePath(path: string) :Array<Token[] >{
  if(! path)return [[]]
  if (path === '/') return [[ROOT_TOKEN]]
  if(! path.startsWith('/')) {
    throw new Error(
      __DEV__
        ? `Route paths should start with a "/": "${path}" should be "/${path}". `
        : `Invalid path "${path}"`)}// if (tokenCache.has(path)) return tokenCache.get(path)!

  function crash(message: string) {
    throw new Error(`ERR (${state})/"${buffer}": ${message}`)}let state: TokenizerState = TokenizerState.Static
  let previousState: TokenizerState = state
  const tokens: Array<Token[]> = []
  // the segment will always be valid because we get into the initial state
  // with the leading /
  letsegment! : Token[]function finalizeSegment() {
    if (segment) tokens.push(segment)
    segment = []
  }

  // index on the path
  let i = 0
  // char at index
  let char: string
  // buffer of the value read
  let buffer: string = ' '
  // custom regexp for a param
  let customRe: string = ' '

  function consumeBuffer() {
    if(! buffer)return

    if (state === TokenizerState.Static) {
      segment.push({
        type: TokenType.Static,
        value: buffer,
      })
    } else if (
      state === TokenizerState.Param ||
      state === TokenizerState.ParamRegExp ||
      state === TokenizerState.ParamRegExpEnd
    ) {
      if (segment.length > 1 && (char === The '*' || char === '+'))
        crash(
          `A repeatable param (${buffer}) must be alone in its segment. eg: '/:ids+.`
        )
      segment.push({
        type: TokenType.Param,
        value: buffer,
        regexp: customRe,
        repeatable: char === The '*' || char === '+'.optional: char === The '*' || char === '? '})},else {
      crash('Invalid state to consume buffer')
    }
    buffer = ' '
  }

  function addCharToBuffer() {
    buffer += char
  }

  while (i < path.length) {
    char = path[i++]

    if (char === '\ \'&& state ! == TokenizerState.ParamRegExp) { previousState = state state = TokenizerState.EscapeNextcontinue
    }

    switch (state) {
      case TokenizerState.Static:
        if (char === '/') {
          if (buffer) {
            consumeBuffer()
          }
          finalizeSegment()
        } else if (char === ':') {
          consumeBuffer()
          state = TokenizerState.Param
        } else {
          addCharToBuffer()
        }
        break

      case TokenizerState.EscapeNext:
        addCharToBuffer()
        state = previousState
        break

      case TokenizerState.Param:
        if (char === '(') {
          state = TokenizerState.ParamRegExp
        } else if (VALID_PARAM_RE.test(char)) {
          addCharToBuffer()
        } else {
          consumeBuffer()
          state = TokenizerState.Static
          // go back one character if we were not modifying
          if(char ! = =The '*'&& char ! = ='? '&& char ! = ='+') i--
        }
        break

      case TokenizerState.ParamRegExp:
        // TODO:is it worth handling nested regexp? like :p(? :prefix_([^/]+)_suffix)
        // it already works by escaping the closing )
        // https://paths.esm.dev/?p=AAMeJbiAwQEcDKbAoAAkP60PG2R6QAvgNaA6AFACM2ABuQBB#
        // is this really something people need since you can also write
        // /prefix_:p()_suffix
        if (char === ') ') {
          // handle the escaped )
          if (customRe[customRe.length - 1] = ='\ \')
            customRe = customRe.slice(0, -1) + char
          else state = TokenizerState.ParamRegExpEnd
        } else {
          customRe += char
        }
        break

      case TokenizerState.ParamRegExpEnd:
        // same as finalizing a param
        consumeBuffer()
        state = TokenizerState.Static
        // go back one character if we were not modifying
        if(char ! = =The '*'&& char ! = ='? '&& char ! = ='+') i--
        customRe = ' '
        break

      default:
        crash('Unknown state')
        break}}if (state === TokenizerState.ParamRegExp)
    crash(`Unfinished custom RegExp for param "${buffer}"`)

  consumeBuffer()
  finalizeSegment()

  // tokenCache.set(path, tokens)

  return tokens
}
Copy the code

The purpose of this function is to convert the path string to an array for subsequent processing. For example, /user will be converted to [[{type: 0, value: ‘user’}]] and /user/:id will be converted to:

[[{type: 0.value: "user"}],
    [{type: 1.value: "id".regexp: "".repeatable: false.optional: false}]]Copy the code

Let’s go back to tokensToParser and see how PathParser is generated

  • re

A regular expression that converts tokens into regular expressions that match the path using tokens passed in from parameters and a list of criteria

const BASE_PATH_PARSER_OPTIONS: Required<_PathParserOptions> = {
  sensitive: false.strict: false.start: true.end: true,}function tokensToParser(
  segments: Array<Token[]>, extraOptions? : _PathParserOptions) :PathParser {
  const options = assign({}, BASE_PATH_PARSER_OPTIONS, extraOptions)
  let pattern = options.start ? A '^' : ' '
  for (const segment of segments) {
      // Iterate through tokens to improve regular expressions
      // TODO: Dig a hole here and analyze how to generate regular expressions later
  }
  if(! options.strict) pattern +='/? '
  if (options.end) pattern += '$'
  else if (options.strict) pattern += '(? : / | $) '
  const re = new RegExp(pattern, options.sensitive ? ' ' : 'i')
  // ...
}
Copy the code
  • score

Calculate a score for the current path, and use the score value to compare subsequent paths, which is equivalent to comparing weights

let score: Array<number[]> = []
for (const segment of segments) {
    const segmentScores: number[] = segment.length ? [] : [PathScore.Root]
    // ...
    score.push(segmentScores)
}
if (options.strict && options.end) {
    const i = score.length - 1
    score[i][score[i].length - 1] += PathScore.BonusStrict
}
Copy the code
  • keys

Saves the dynamic parameters of a route

const keys: PathParserParamKey[] = []
for (const segment of segments) {
    // ...
    if (token.type === TokenType.Param) {
        const { value, repeatable, optional, regexp } = token
        keys.push({
          name: value,
          repeatable,
          optional,
        })
    }
    // ...
}
Copy the code
  • parse

Pass in the path argument, then get the dynamic argument object based on the RE, and then iterate over the result

function parse(path: string) :PathParams | null {
    const match = path.match(re)
    const params: PathParams = {}

    if(! match)return null

    for (let i = 1; i < match.length; i++) {
      const value: string = match[i] || ' '
      const key = keys[i - 1]
      params[key.name] = value && key.repeatable ? value.split('/') : value
    }

    return params
}
Copy the code
  • stringify

This method passes in the params object and returns the path of the parameter object combined with path instead of the parameter value

function stringify(params: PathParams) :string {
    let path = ' '
    // for optional parameters to allow to be empty
    let avoidDuplicatedSlash: boolean = false
    for (const segment of segments) {
      if(! avoidDuplicatedSlash || ! path.endsWith('/')) path += '/'
      avoidDuplicatedSlash = false

      for (const token of segment) {
        if (token.type === TokenType.Static) {
          path += token.value
        } else if (token.type === TokenType.Param) {
          const { value, repeatable, optional } = token
          const param: string | string[] = value in params ? params[value] : ' '

          if (Array.isArray(param) && ! repeatable)throw new Error(
              `Provided param "${value}" is an array but it is not repeatable (* or + modifiers)`
            )
          const text: string = Array.isArray(param) ? param.join('/') : param
          if(! text) {if (optional) {
              // if we have more than one optional param like /:a? -static we
              // don't need to care about the optional param
              if (segment.length < 2) {
                // remove the last slash as we could be at the end
                if (path.endsWith('/')) path = path.slice(0, -1)
                // do not append a slash on the next iteration
                else avoidDuplicatedSlash = true}}else throw new Error(`Missing required param "${value}"`)
          }
          path += text
        }
      }
    }

    return path
}
Copy the code

It’s not easy to go through these complicated steps and get a complete Matcher object

  • The originalMatcher property is then assigned to the originalMatcher as matcher if it is the first assignment. The matcher is not reassigned, but added to the OriginalRecord. alias array

  • 4, then according to the ‘children’ in mainNormalizedRecord conditions determine whether zi lu by, if there is zi lu by the traversal mainNormalizedRecord. Children array, and call addRoute method, the parameters are: children[i], matcher, originalRecord && originalRecord.children[i]

  • 5. Finally call insertMatcher(matcher) to add matcher to matchers and update the matcherMap

function insertMatcher(matcher: RouteRecordMatcher) {
  let i = 0
  while (
    i < matchers.length &&
    comparePathParserScore(matcher, matchers[i]) >= 0
  )
    i++
  matchers.splice(i, 0, matcher)
  if(matcher.record.name && ! isAliasRecord(matcher)) matcherMap.set(matcher.record.name, matcher) }Copy the code

The addRoute method is completed

  • Resolve method

The resolve method returns the MatcherLocation object, which contains the following attributes: name, path, Params, matched, meta. The function is to match the route based on the passed location and find the route information corresponding to the matcher corresponding to the location

function resolve(location: Readonly
       
        , currentLocation: Readonly
        
       ) :MatcherLocation {
  let matcher: RouteRecordMatcher | undefined
  let params: PathParams = {}
  let path: MatcherLocation['path']
  let name: MatcherLocation['name']

  if ('name' in location && location.name) {
    matcher = matcherMap.get(location.name)

    if(! matcher)throw createRouterError<MatcherError>(ErrorTypes.MATCHER_NOT_FOUND, {
        location,
      })

    name = matcher.record.name
    params = assign(
      paramsFromLocation(
        currentLocation.params,
        matcher.keys.filter(k= >! k.optional).map(k= > k.name)
      ),
      location.params
    )
    path = matcher.stringify(params)
  } else if ('path' in location) {
    path = location.path

    matcher = matchers.find(m= > m.re.test(path))
    if (matcher) {
      params = matcher.parse(path)!
      name = matcher.record.name
    }
  } else {
    matcher = currentLocation.name
      ? matcherMap.get(currentLocation.name)
      : matchers.find(m= > m.re.test(currentLocation.path))
    if(! matcher)throw createRouterError<MatcherError>(ErrorTypes.MATCHER_NOT_FOUND, {
        location,
        currentLocation,
      })
    name = matcher.record.name
    params = assign({}, currentLocation.params, location.params)
    path = matcher.stringify(params)
  }

  const matched: MatcherLocation['matched'] = []
  let parentMatcher: RouteRecordMatcher | undefined = matcher
  while (parentMatcher) {
    matched.unshift(parentMatcher.record)
    parentMatcher = parentMatcher.parent
  }

  return {
    name,
    path,
    params,
    matched,
    meta: mergeMetaFields(matched),
  }
}
Copy the code
  • RemoveRoute method

The method takes a parameter matcherRef, the parameter type can be passed in the route name attribute or matcher object, and then find the corresponding matcher or matcher index through the matcherRef, delete the corresponding matcher in the matcherMap and matchers. It then recursively removes references to the matcher object in matcher. Children and matcher. Alias

function removeRoute(matcherRef: RouteRecordName | RouteRecordMatcher) {
  if (isRouteName(matcherRef)) {
    const matcher = matcherMap.get(matcherRef)
    if (matcher) {
      matcherMap.delete(matcherRef)
      matchers.splice(matchers.indexOf(matcher), 1)
      matcher.children.forEach(removeRoute)
      matcher.alias.forEach(removeRoute)
    }
  } else {
    let index = matchers.indexOf(matcherRef)
    if (index > -1) {
      matchers.splice(index, 1)
      if (matcherRef.record.name) matcherMap.delete(matcherRef.record.name)
      matcherRef.children.forEach(removeRoute)
      matcherRef.alias.forEach(removeRoute)
    }
  }
}
Copy the code
  • GetRoutes method

This method returns the Matchers array directly

function getRoutes() {
    return matchers
}
Copy the code
  • GetRecordMatcher method

This method is very simple, just get the corresponding matcher object from the matcherMap by route name

function getRecordMatcher(name: RouteRecordName) {
    return matcherMap.get(name)
}
Copy the code

Install the VueRouter instance

Through the above analysis, we know that:

Create a VueRouter object using the createRouter(options) method. This method takes a configuration object that must provide two properties: History and Routes History You can create different types of History objects as needed using the three methods provided by VueRouter, which provide route properties and route jump methods

VueRouter creates a matcher object based on the Routes configuration. With the matcher object, VueRouter provides attributes and methods related to route configuration, such as adding routes, matching routes, and removing routes

Now that we have created the VueRouter instance object, how do we associate the VueRouter instance with the Vue instance

In the document sample code, add the VueRouter instance object to the Vue instance object via app.use

Create and mount the root instance
const app = Vue.createApp({})
// Ensure that the _use_ routing instance enables the entire application to support routing.
app.use(router)
Copy the code

When app.use(router) executes, it actually calls the install method of the VueRouter instance

export const START_LOCATION_NORMALIZED: RouteLocationNormalizedLoaded = {
  path: '/'.name: undefined.params: {},
  query: {},
  hash: ' '.fullPath: '/'.matched: [].meta: {},
  redirectedFrom: undefined,}export function createRouter(options: RouterOptions) :Router {
  // shallowRef: Creates a ref that tracks its own.value changes but does not make its value responsive.
  const currentRoute = shallowRef<RouteLocationNormalizedLoaded>(
    START_LOCATION_NORMALIZED
  )

  let routerHistory = options.history

  const router: Router = {

    install(app: App) {
      const router = this
  
      // In the vUE instance, register the global routing components RouterLink and RouterView
      app.component('RouterLink', RouterLink)
      app.component('RouterView', RouterView)
  
      // Assign config.globalProperties.$router to the current VueRouter instance in the vue instance
      app.config.globalProperties.$router = router
      / * * * when reading app. Config. GlobalProperties. $route, * return unref (currentRoute), namely the current routing information, the initial value for the path for ` / ` object * /
      Object.defineProperty(app.config.globalProperties, '$route', {
        enumerable: true.get: () = > unref(currentRoute),
      })

      if (
        isBrowser &&
        // Avoid multiple pushes when using the router in multiple applications. This is false only if it is stated at first install! started && currentRoute.value === START_LOCATION_NORMALIZED ) { started =true
        // Jump to the corresponding route in the browser URL
        push(routerHistory.location)
      }
      
      // Copy the currentRoute object and convert it to the reactive object reactiveRoute, which can be retrieved in the component via Inject routeLocationKey
      const reactiveRoute = {} as {
        [k in keyof RouteLocationNormalizedLoaded]: ComputedRef<
          RouteLocationNormalizedLoaded[k]
        >
      }
      for (let key in START_LOCATION_NORMALIZED) {
        // @ts-ignore: the key matches
        reactiveRoute[key] = computed(() = > currentRoute.value[key])
      }
      
      // Inject router providers into the vue instance. The component uses Inject to receive these values
      SRC /injectionSymbols.ts; // This Symbol is a Symbol
      app.provide(routerKey, router)
      app.provide(routeLocationKey, reactive(reactiveRoute))
      app.provide(routerViewLocationKey, currentRoute)
  
      // Intercepts the vue instance unmount method, resets some properties and events unbind when the vue instance is unmounted, and then executes the vue instance unmount method
      let unmountApp = app.unmount
      installedApps.add(app)
      app.unmount = function () {
        installedApps.delete(app)
        if (installedApps.size < 1) {
          removeHistoryListener()
          currentRoute.value = START_LOCATION_NORMALIZED
          started = false
          ready = false
        }
        unmountApp()
      }
    },
  }
}
Copy the code

In summary, the install method does a few things:

  • Register two routing components as VUE global components
  • In app. Config. GlobalProperties add the router and the router and the router and the route attributes, the router is VueRouter instance object itself, the router is VueRouter instance object itself, Router is the VueRouter instance object itself, and route is the route object corresponding to the current location
  • If this is the first install, the push method is used to jump to the route corresponding to the URL
  • Inject three VueRouter related providers
  • Intercept the unmount method of the Vue instance and perform the VueRouter related unmount before the unmount method is called

conclusion

Typescript makes it relatively easy to read source code, and provides a good understanding of what each variable does through type definitions. Starting from the creation of VueRouter instance object, we have a certain understanding of the basic implementation principle of routing, but also realize that it is not a very simple thing to achieve a complete routing function, and need to consider a lot of boundary problems. Due to my limited ability, THERE are still many details I cannot understand. Reading the source code is always a good thing, no matter how much you can understand.