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

在线体验各类最新模型,更有模型 免费Token 额度领取!
立即体验
简介: 之前的文章讲了, 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
相关文章
|
10月前
|
JavaScript
Vue中如何实现兄弟组件之间的通信
在Vue中,兄弟组件可通过父组件中转、事件总线、Vuex/Pinia或provide/inject实现通信。小型项目推荐父组件中转或事件总线,大型项目建议使用Pinia等状态管理工具,确保数据流清晰可控,避免内存泄漏。
782 2
|
9月前
|
缓存 JavaScript
vue中的keep-alive问题(2)
vue中的keep-alive问题(2)
658 137
|
人工智能 JavaScript 算法
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
1248 0
|
人工智能 JSON JavaScript
VTJ.PRO 首发 MasterGo 设计智能识别引擎,秒级生成 Vue 代码
VTJ.PRO发布「AI MasterGo设计稿识别引擎」,成为全球首个支持解析MasterGo原生JSON文件并自动生成Vue组件的AI工具。通过双引擎架构,实现设计到代码全流程自动化,效率提升300%,助力企业降本增效,引领“设计即生产”新时代。
817 1
|
12月前
|
JavaScript 安全
在 Vue 中,如何在回调函数中正确使用 this?
在 Vue 中,如何在回调函数中正确使用 this?
570 0
|
JavaScript
vue实现任务周期cron表达式选择组件
vue实现任务周期cron表达式选择组件
1527 4
|
JavaScript UED
用组件懒加载优化Vue应用性能
用组件懒加载优化Vue应用性能
|
JavaScript 数据可视化 前端开发
基于 Vue 与 D3 的可拖拽拓扑图技术方案及应用案例解析
本文介绍了基于Vue和D3实现可拖拽拓扑图的技术方案与应用实例。通过Vue构建用户界面和交互逻辑,结合D3强大的数据可视化能力,实现了力导向布局、节点拖拽、交互事件等功能。文章详细讲解了数据模型设计、拖拽功能实现、组件封装及高级扩展(如节点类型定制、连接样式优化等),并提供了性能优化方案以应对大数据量场景。最终,展示了基础网络拓扑、实时更新拓扑等应用实例,为开发者提供了一套完整的实现思路和实践经验。
1800 78
|
缓存 JavaScript 前端开发
Vue 基础语法介绍
Vue 基础语法介绍
|
JavaScript 前端开发 开发者
Vue 自定义进度条组件封装及使用方法详解
这是一篇关于自定义进度条组件的使用指南和开发文档。文章详细介绍了如何在Vue项目中引入、注册并使用该组件,包括基础与高级示例。组件支持分段配置(如颜色、文本)、动画效果及超出进度提示等功能。同时提供了完整的代码实现,支持全局注册,并提出了优化建议,如主题支持、响应式设计等,帮助开发者更灵活地集成和定制进度条组件。资源链接已提供,适合前端开发者参考学习。
786 17

推荐镜像

更多
  • DNS