vue-router 如何找到待渲染的 vue 组件?详解 route 别名处理,vue-router Matcher 解析(二)

简介: 之前的文章讲了, vue-router 找到待渲染的 vue 组件时,对 route route 的处理过程,但是还有 route 的 alias(别名) 是如何操作没有解释,对于别名在 vue-ro

前情提要

之前的文章vue-router 如何找到待渲染的 vue 组件?vue-router Matcher 解析(一)讲了, vue-router 找到待渲染的 vue 组件时,对 route 的处理过程,因此本篇文章将会默认 route 已经被处理了,即为 RouteRecordNormalized 路由记录的标准版本

本篇文章会介绍 matcher 处理 route 的别名部分, 即 vue-router 文档的重定向和别名

注:本篇文章关于 matcher 的解析和源码均对应 vue-router4 即 vue3 版本的 router

注意区分文章中的, routes 和 route, route 指 { path: "/", Redirect: "/test" }, routes 指 [route,route,...]

别名处理

参考文档中对别名处理 API 的介绍, vue-router 文档 - 别名

总之别名最后实现的效果就是下面这样的

const route = {
  path: "/users",
  component: User,
  // 英语水平太差, 开摆了, 随便找一个单词
  alias: "/humans",
  children: {
    { path: '', component: UserList, alias: ['/people', 'list'] },
  }
}

上面的 route 能匹配下面的路径

// - /users
// - /people
// - /users/list
// - /humans/list
// - /humans

所以别名(alias)在 vue-router 上是一种怎样的存在方式呢?怎么操作的呢?前排提示, 其实再复制语法路由记录, 而从上面其实可以看出来, vue-router 其实在 alias(别名的处理上) 做了两件事

  1. 添加别名路由
  2. 递归更新别名子路由

接下来将细嗦一下 vue-router 是怎么解决上面这两个问题的

添加别名路由

在 router/src/matcher/index.ts 86 处会对 normalizedRecords(routes 的标准化版本数组), 进行一个数组化处理, 其实就是为了方便我们添加别名,所以默认加了别名,可以参考一下面的一个数据结构描述

// 源码
const routes = { path: "/users", alias: "/humans" };
// vue-router 处理后
const normalizedRecords = [{ path: "/users" }, { path: "/humans" }];

vue-router 对添加别名路由的操作其实也是很简单粗暴,一个 for 循环,像下面这样

if ("alias" in record) {
  const aliases =
    typeof record.alias === "string" ? [record.alias] : record.alias!;
  for (const alias of aliases) {
    normalizedRecords.push({
      // alias route: RouteRecordNormalized
    });
  }
}

注意一个小细节,看下面源码

const aliases =
  typeof record.alias === "string" ? [record.alias] : record.alias!;

但这里应该是负责 record.alias 为 string[] 的情况的, alias 在 route 对应的接口 _RouteRecordBase 中实际上是一个可选的属性,即可能会是 undefined, 因此这里其实是对其做的一个空值处理, 表示 record.alias 一定会有值,不可能会是 null 或者 undefined, 但实际上即使 record.alias = undefined || null, vue-router 也是能运行的但是会报错, 过不了 for (const alias of aliases) {//...}

添加子路由并不是把 path 改了就行了,还得给子路由找爸爸, components 和 aliasOf 就是需要依靠 originalRecord 去判断, 如果存在父路由, 子路由就需要保留这个值, 这个其实和递归更新子路由有关

normalizedRecords.push(
  assign({}, mainNormalizedRecord, {
    components: originalRecord
      ? originalRecord.record.components
      : mainNormalizedRecord.components,
    path: alias,
    aliasOf: originalRecord ? originalRecord.record : mainNormalizedRecord,
  }) as typeof mainNormalizedRecord
);

这里可以讲一下 vue-router 有一个设计的巧妙之处

for (const normalizedRecord of normalizedRecords) {
  matcher = createRouteRecordMatcher(normalizedRecord, parent, options);

  if (originalRecord) {
    originalRecord.alias.push(matcher);
  } else {
    originalMatcher = originalMatcher || matcher;
  }

  originalRecord = originalRecord || matcher;
}

const routes = { path: "/users", alias: "/humans" }; 为例,对应上面源码的一个数据结构, originalRecord 指的就是 "/users"

const normalizedRecords = ["/users", "/humans"];

它的目的就是使 "/users" 生成的 Matcher 需要和 "/humans"(alias) 生成的 Matcher 建立联系, 也就是 originalRecord.alias.push(matcher); 这一步, "/user" 作为 normalizedRecord 第一次遍历时是没有 originalRecord, 然后通过 || 赋值, || 其实可以叫做选择运算符, 它会选择第一个为真的值, 比如

console.log(false || 1); // 1
console.log(1 || false); // 1

所以第一次遍历的时候, 遍历 original "/users", originalRecord = undefined || matcherByUser, 而第二次遍历的时候就是 originalRecord = matcherByUser || matcherByHuman, 因此可以整个的遍历的 original 都将指向 "/users"

递归更新别名子路由

递归更新别名子路由,里用到了多叉树遍历算法(leetcode 有详细的解法, labuladong 也讲过),如果把源码抽调出来的话你可以看到, vue-router4 的递归其实和多叉树遍历框架同根同源

function addRoute() {
  for (const normalizedRecord of normalizedRecords) {
    if ("children" in mainNormalizedRecord) {
      const children = mainNormalizedRecord.children;
      for (let i = 0; i < children.length; i++) {
        addRoute();
      }
    }
  }
}
// 多叉树遍历框架
const traverse = (root) => {
  if (root === null) {
    return;
  }
  for (let i = 0; i < root.children.length; i++) {
    traverse(root.children[i]);
  }
};

之所以多了一层循环是因为, vue-router 的 alias 和 original 同时分裂的, 前面也说过像 route 一开始就会做数组化处理,至此达成闭环

// 源码
const routes = { path: "/users", alias: "/humans" };
// vue-router 处理后
const normalizedRecords = [{ path: "/users" }, { path: "/humans" }];

通过这部分我们可以知道, vue-router 处理 routes 这种多叉树结构时用的是递归式的遍历框架

总结

最后讲一下阅读 vue-router 时的一些小技巧吧,一开始看 matcher 这部分的代码的时候,看了各个部分的一个调用的关系,看着看着就掉入了细节陷阱,浪费了很长时间,长时间纠结于设计者在部分源码的考虑,和 TS 的类型,想要快速理解一部分源码的使用以及设计者的考虑,可以尝试去 Chrome 去调试,在知道了源码模块之间的调用关系之后就可以,设计测试用例,比如 vue-router 文档中给出的 route, 就是非常好的测试用例, 然后运行到 Chrome 去调试, Chrome 的调试优于 Vs code 调试, 加上 vite 的加持, 可以说快到起飞, 在打断点一行行运行的时候, 你可以看到设计者在源码上的思考,调试这种动态的过程,是比你直接看源码要高效很多的

又是一个小细节

vue-router matcher 部分的 alias(别名处理) 就先讲到这里,如果你对 vue-router4 的源码感兴趣的话,觉得文章讲的还行,可以看之后发的几篇源码解析(关注就能看到后续更新), Matcher 部分文章如下

  1. vue-router 如何找到待渲染的 vue 组件?vue-router Matcher 解析(一))- 标准化 route
  2. vue-router 如何找到待渲染的 vue 组件?vue-router Matcher 解析(三)- 没写 - 第二次处理 routes
相关文章
|
23小时前
|
JavaScript
Vue实战-将通用组件注册为全局组件
Vue实战-将通用组件注册为全局组件
5 0
|
2天前
|
编译器
vue3组件TS类型声明实例代码
vue3组件TS类型声明实例代码
4 0
|
2天前
|
JavaScript 前端开发 IDE
vue3基础: 组件注册
vue3基础: 组件注册
10 0
|
2天前
|
JavaScript
vue3使用element-plus 树组件(el-tree)数据回显
vue3使用element-plus 树组件(el-tree)数据回显
5 0
|
3天前
|
缓存 JavaScript
在 Vue 组件中使用计算属性和侦听器来响应路由变化
Vue Router 中,计算属性和侦听器常用于根据路由变化更新组件状态。计算属性缓存依赖,当路由参数改变时自动更新,如示例中的 `userId`。侦听器则监听 `$route` 变化,执行相应操作,例如在 `currentUserId` 示例中响应 `userId` 更新。计算属性适合简单变化,而异步操作或复杂场景可选用侦听器。Vue 3 中,`watchEffect` 减少了部分侦听场景的复杂性。总之,它们用于组件内部响应路由变化,而非直接处理路由逻辑。
11 4
|
4天前
|
JavaScript
Vue3的 组件事件
Vue3的 组件事件
17 0
|
4天前
|
存储 JavaScript
vue3组件之间传值通讯
vue3组件之间传值通讯
8 0
|
4天前
|
资源调度 JavaScript 前端开发
vue3怎么调用vant中的icon组件
vue3怎么调用vant中的icon组件
17 4
|
4天前
|
JavaScript
vue实现递归组件
vue实现递归组件
12 0
|
5天前
|
缓存 JavaScript
vue 中 keep-alive 组件的作用
vue 中 keep-alive 组件的作用
11 1

推荐镜像

更多