Vue-Router源码分析(二): 路由映射表创建

js 复制代码
const router = new VueRouter({
  routes: [
    {
      path: '/',
      component: Home
    },
    {
      path: '/user',
      component: User,
      children: [
        {
          path: 'profile',
          component: UserProfile
        },
        {
          path: 'posts',
          component: UserPosts
        },
        {
          path: 'about',
          component: About
        }
      ]
    }
  ]
})

在我们使用Vue-Router配置路由信息时,这段代码是最常见的,routes对象接受一个对象数组,其中每个对象代表着一个路由信息,最核心就是path、component属性: path字段表示location的路由信息,component表示改路由对应的组件,当然还有一些其他属性,name、meta等等。

根据以上配置,Vue-Router在初始化时做了以下操作,保证path 一定能对应指定的component组件。

树的扁平化

在源码中:src/create-route-map.js文件中,将用户传递进来的routes属性遍历执行addRouteRecord方法

js 复制代码
routes.forEach(route => {
   addRouteRecord(pathList, pathMap, nameMap, route, parentRoute)
})

该方法中声明了一个RouteRecord对象,用来存储遍历的路由信息:

js 复制代码
  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, //路由name
    parent, // 路由对应的父组件,这个属性很重要, 在递归是找到父组件的依据
    matchAs,
    redirect: route.redirect, // 重定向
    beforeEnter: route.beforeEnter, //钩子函数
    meta: route.meta || {}, //元信息
    props:
      route.props == null
        ? {}
        : route.components
          ? route.props
          : { default: route.props } //组件props
  }

接着判断route是否有children配置属性,执行递归操作。

js 复制代码
  if (route.children) {

   ......

    route.children.forEach(child => {

      ......

      // 递归执行,
      addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs)
    })
  }

这里有个点需要注意,

  • 在每次递归是都会去判断当前路由信息是否有parent,如果存在就要去拼接parent路由path和当前子路由path路劲,具体在normalizePath方法中:
js 复制代码
function normalizePath (
  path: string,
  parent?: RouteRecord,
  strict?: boolean
): string {
  // 正则相关
  if (!strict) path = path.replace(/\/$/, '')  
  if (path[0] === '/') return path //判断是否是根路由信息, "/"
  if (parent == null) return path // 不存在parent,说明当前 path就是为第一级路由信息,返回当前path路径即可
  return cleanPath(`${parent.path}/${path}`) // 存在,说明当前路由为childeren配置项的路由信息,拼接父路由和子路由path
}

那parent录音信息是在哪里传入的? 是在遍历第一次的路由信息时传入的。可以看递归时传入的参数:addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs) 。child参数即为当前子路由是所归属的父路由信息。也就是上面声明的RouteRecord 对象。

经过递归之后,record对象就会是这个样子

js 复制代码
 record = {
   path: '/',
   component: <Home>
 }
 record = {
   path: '/user/profile',
   component: <UserProfile>
 }
 record = {
   path: '/user/posts',
   component: <UserPosts>
 }
 record = {
   path: '/user/about',
   component: <About>
 }

createRouteMap方法中会有一个pathMap对象用来保存扁平化之后的路由对象信息。

js 复制代码
  // 先读取oldPathMap 的值,oldPathMap是动态路由添加方法`addRoutes`提供的参数,oldPathMap就是在项目初始化声明的静态路由配置信息。项目初始化时肯定没有oldPathMap,所以创建了一个空对象。
  const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null)

回到addRouteRecord方法:

js 复制代码
  // 不能定义重复的路由信息,因为只会执行一次
  if (!pathMap[record.path]) {
    pathList.push(record.path)
    pathMap[record.path] = record
  }

可以看到还有另外一个数组对象,pathList ,这里用处就是保证根路由'/' 在数组中第一位。主要还是pathMap对象。

这里的pathMap对象就是扁平化之后的数据:

js 复制代码
pathMap = {
  '/': record,
  '/user/profile': record,
  '/user/posts': record,
  '/user/about': record,
  ...
  ...
}

其实还有一个nameMap,其原理跟pathMap一样,不断递归得到一个扁平化后的nameMap路由信息,只不过这时候对应的信息由原先path 映射 组件 换成了 name 映射组件。

到这里,由映射表创建已经完成,其实就是树的深度优先遍历的过程,最后将用户的路由配置扁平化成一个包含所有路由信息的对象。

相关推荐
一杯奶茶¥6 小时前
基于springboot的失物招领管理系统带万字文档 校园失物招领管理系统 失物认领管理系统java springboot vue
java·vue.js·spring boot·java项目
OpenTiny社区11 小时前
这次更新太良心!GenUI SDK v1.2.0 轻量化 + 稳流式 + 超强 Playground
前端·vue.js·ai编程
秃头网友小李12 小时前
前端难点:Element Plus 样式覆盖 —— :deep()、CSS 变量与滚动状态类名
前端·vue.js
英勇无比的消炎药15 小时前
吃透 Sender 交互逻辑:提交快捷键事件与方法实战运用
vue.js
Agatha方艺璇16 小时前
VUE复习笔记
前端·vue.js
chushiyunen17 小时前
vue el-pagination实现分页
javascript·vue.js·elementui
wanger6118 小时前
Vue学习笔记
前端·javascript·vue.js
阿猫的故乡18 小时前
Vue动态组件+异步组件实战:Tab切换、按需加载、KeepAlive缓存,一次搞定
前端·vue.js·缓存
阿猫的故乡18 小时前
Vue3自定义插件:封装一个全局消息提示插件,所有组件都能直接用
前端·javascript·vue.js