Vue Router源码解读

102 阅读2分钟

Vue Router源码解读

to0107

"version": "3.5.3"

VueRouter.png

入口

install.js

Vue原型上混入router对应的逻辑

这里能看到root应用上,调用了router实例的init方法

子组件上所有的_routerRoot都指向根组件的_routerRoot

Vue.mixin({
    beforeCreate () {
      if (isDef(this.$options.router)) {
        this._routerRoot = this
        this._router = this.$options.router
        this._router.init(this)
        Vue.util.defineReactive(this, '_route', this._router.history.current)
      } else {
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
      }
      registerInstance(this, this)
    },
    destroyed () {
      registerInstance(this)
    }
})

VueRouter

index.js

createMatcher

createMatcher方法把routes循环,对应生成 match, addRoute, getRoutes, addRoutes 4个方法

this.matcher = createMatcher(options.routes || [], this)

createRouteMap

内部其实调用这个createRouteMap生成对应的routes的map

  • pathList: 路径的数组
  • pathMap: 以路径为key, value是RouteRecord对象
  • nameMap: 以路由名字为key, value是RouteRecord对象

以上操作都是为了索引方法而做的一些列操作

addRouteRecord

把对应的route组装成一个对象, 然后分别存到 pathList、pathMap、nameMap中去

  const record: RouteRecord = {
    path: normalizedPath,
    regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
    components: route.components || { default: route.component },
    alias: route.alias
      ? typeof route.alias === 'string'
        ? [route.alias]
        : route.alias
      : [],
    instances: {},
    enteredCbs: {},
    name,
    parent,
    matchAs,
    redirect: route.redirect,
    beforeEnter: route.beforeEnter,
    meta: route.meta || {},
    props:
      route.props == null
        ? {}
        : route.components
          ? route.props
          : { default: route.props }
  }

  if (route.children) {
    
    route.children.forEach(child => {
      const childMatchAs = matchAs
        ? cleanPath(`${matchAs}/${child.path}`)
        : undefined
      addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)
    })
  }

  // pathMap、pathList中做记录
  if (!pathMap[record.path]) {
    pathList.push(record.path)
    pathMap[record.path] = record
  }

path-to-regex

最重要的一步就是使用path-to-regex这个库生成对应的正则匹配对象

以路径为例/crew/crew-archives

调用后会生成以下对象

{
    keys: [],
    lastIndex: 0,
    dotAll: false,
    flags: "i",
    global: false,
    hasIndices: false,
    ignoreCase: true,
    multiline: false,
    source: "^\\/crew\\/crew-archives(?:\\/(?=$))?$",
    sticky: false,
    unicode: false,
}

match

  • 存在name
    • 从nameMap中寻找
    • 如果有参数,也就是path-to-regex生成的keys中如果有对应的参数,则去参数列表中去匹配
    • 最后生成对应的路由对象,这里路由对象用Object.freeze进行包装
  • 不存在name
    • 遍历pathList, 根据正则进行匹配出对应的路由
function match (
    raw: RawLocation,
    currentRoute?: Route,
    redirectedFrom?: Location
  ): Route {
    const location = normalizeLocation(raw, currentRoute, false, router)
    const { name } = location

    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 (typeof location.params !== 'object') {
        location.params = {}
      }

      if (currentRoute && typeof currentRoute.params === 'object') {
        for (const key in currentRoute.params) {
          if (!(key in location.params) && paramNames.indexOf(key) > -1) {
            location.params[key] = currentRoute.params[key]
          }
        }
      }

      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]
        if (matchRoute(record.regex, location.path, location.params)) {
          return _createRoute(record, location, redirectedFrom)
        }
      }
    }
    // no match
    return _createRoute(null, location)
  }