vue-router 还给路由排了序?解析路由匹配,vue-router Matcher 解析(三)

简介: 之前的两篇文章讲了, vue-router 的 Matcher 对初始的 routes 进行了标准化(normalized)处理以及别名(alias)处理,但还有matcher 处理 route 的匹配

前情提要

之前的两篇文章讲了, vue-router 的 Matcher 对初始的 routes 进行了标准化(normalized)处理以及别名(alias)处理,详情链接参考文章尾部

本篇文章会介绍 matcher 处理 route 的匹配部分, 即 vue-router 文档的路由的匹配语法

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

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

matcher 数组

之前提到过, 类似 routes 这样的数组其实是一种多叉树结构, 然后整个 matcher 数组生成的过程中使用了递归式的多叉树遍历,并在遍历过程中处理了别名以及别名子路由,将子路由的 path 修改为正确的, 比如 /a/1, 其实这个在 matcher 里面都是一个次要的功能,真正的功能是去生成 matchers 数组,对,虽然之前文章花了重点介绍 addRoute,但是不要被它 语义 给迷惑了,它的真正目的是给 matchers 添加 route 转化的 matcher(小细节)

又是一个小细节

// routes 其实是多叉树
const routes = [
  {
    path: "/",
    component: Home,
  },
  {
    path: "/a",
    alias: "/ab",
    component: null,
    children: [{ component: null, path: "1" }],
  },
  {
    path: "/b",
    component: Home,
  },
];

区分路由

前面说了 addRoute 目的是给 matchers 添加 route 转化的 matcher, 这个证明在于 addRoute 的底部 router/src/matcher/index.ts 179

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

insertMatcher() 就是今天这篇文章的主角

为什么说区分路由? 我们先看一下 insertMatcher 的源码 router/src/matcher/index.ts 214

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);
}

insertMatcher() 主要作用就是找到 matcher(由 route 转化的) 在 matchers 中的位置, 这也就是我标题为什么说 matchers 是一个有顺序的数组, 而区分路由就是指 matchers 和 matcherMap 两个数据结构, 一个数组一个哈希表, 在上面标注的源码中有一个性能优化的点, 就是命名路由的 matcher 是有另一份存放在 matcherMap 里面的, 这很重要, 因为 Map 这种数据结构的查询时间复杂度是 O(1), 是很快的, 所以我们在编写路由的, 应该尽可能的使用命名路由, 它的查询时间远远快于普通路由, 普通路由的查询时间复杂度是 O(n), matcher 里面的 resolve 就是我们匹配路由是调用的方法, 里面其实就写了命名路由和普通路由的匹配方式

// router/src/matcher/index.ts 283 resolve()
matcher = currentLocation.name
  ? matcherMap.get(currentLocation.name)
  : matchers.find((m) => m.re.test(currentLocation.path));

为了方便理解, 请参考下面原始 routes 的数据结构和 matchers 的数据结构

const routes = [
  {
    path: "/",
    component: Home,
  },
  {
    path: "/a",
    alias: "/ab",
    component: null,
    children: [{ component: null, path: "1" }],
  },
  {
    path: "/b",
    component: Home,
  },
];

matchers

1.png

前排提示, score 是一个伏笔

路径排名

前面说了, insertMatcher() 是通过 while() 遍历找到 matchers 的正确的插入位置的, 源码如下

// 生成 matchers 的代价是 O(n^2)
while (i < matchers.length && comparePathParserScore(matcher, matchers[i]) >= 0)
  i++;
// 模拟 matchers 的生成过程
1. [/]
2. [/,/a/1]
3. [/,/a,/a/1]
4.
...

小细节来了, 这个 matchers 它其实是一个有顺序的数组, 不是乱排的, 它有个路径排名的功能, 里面核心有两个函数 comparePathParserScorecompareScoreArray

简单介绍一下 matcher 它这个排名的实现, 生成 matcher 的时候, 根据你传入的 route 的 path 打分, 然后根据这个分的高低去给你排序看你适合排在哪里, 它的主要目的是什么呢?这就要说到这个设计者在北京分享的一个讲座的小细节了

const routes = [
  {
    path: "/movie/:id",
  },
  {
    path: "/movie/new",
  },
];

像上面这个路由, 如果你传入 /movie/new 其实两个都是满足条件的, 但是很明显第二个才是我们想要的, 所以为了正确的匹配我们必须让 /movie/new 在之前 /movie/:id 被匹配到, 注意, 我们前面说过, 普通路由匹配的方式就是 matchers.find(), 就是从 0 到 n 匹配是返回第一个适合的, 所以为什么需要路径排名? 为什么 matchers 是一个有顺序的数组? 就是要让 /movie/new 这种静态路由能够比 /movie/:id 这种动态路由先匹配到, 先静后动

回到它的底层实现, 看之前关于 matchers 的数据结构截图, 里面有个属性叫 score, 它里面存放的就是根据 path 打分得到的 number 数组, 像 /movie/new 这种静态路由的分数要比 /movie/:id 这种动态路由高, 因此匹配的时候就不会出错, 可以参考下面 Chrome 里面的调试截图

2.png

3.png

4.png

剩余的排名就是按照源码(指你写的 routes)的顺序, 比如

// routes
const routes = [
  {
    path: "/a",
  },
  {
    path: "/b",
  },
];
// matchers
const matchers = ["/a", "/b"];

说实话,我是有点失望的, vue-router matcher 的 diff 算法, 和存放 matcher 的数组似乎不是我认识的数据结构和算法,我一开始看到 vue-router 的几个特征, 1. N 叉树, 2. matcher 用数组存放 3. matchers 生成顺序依靠 vue-router 自定义得分, 几乎那两个数据结构是呼之欲出,但是竟然不是,这两个数据结构就是, 二叉搜索树(BST)二叉堆(优先级队列), 为什么认为是这两个数据结构,一是我认为二叉搜索树的查找效率为 O(logn) 很快, 二就是我单纯的看到数组以为会用到二叉堆

但是回过头一想, routes 是 N 叉树, 可能上面这两种数据结构并不合适, 所以纯属是我刷题刷魔怔了

image.png

总结

vue-router matcher 基本都讲完了, 算是非常细节了, 百度 google 都找不到几篇讲 matcher 的, 更不要说是 vue-router4 的源码解析, 不知道还会不会继续写关于 matcher 部分的内容, 其实 matcher 部分还有 path 的解析没有细细展开讲解, 可能还会再出一篇, 剩下的就是 matcher 和 history 以及 component 的联调解析了

vue-router matcher 部分的 path ranking(路径排名) 就先讲到这里,如果你对 vue-router4 的源码感兴趣的话,觉得文章讲的还行,可以看之后发的几篇源码解析(关注就能看到后续更新, 欢迎点赞催更), Matcher 部分文章如下

  1. vue-router 如何找到待渲染的 vue 组件?vue-router Matcher 解析(一)) - 标准化 route
  2. vue-router 如何找到待渲染的 vue 组件?详解 route 别名处理,vue-router Matcher 解析(二 - 别名处理
  3. [vue-router 如何匹配路由?解析路径排名,vue-router Matcher 解析(三)] - 路径排名
相关文章
|
人工智能 JavaScript 算法
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
1590 0
|
JavaScript 数据可视化 前端开发
基于 Vue 与 D3 的可拖拽拓扑图技术方案及应用案例解析
本文介绍了基于Vue和D3实现可拖拽拓扑图的技术方案与应用实例。通过Vue构建用户界面和交互逻辑,结合D3强大的数据可视化能力,实现了力导向布局、节点拖拽、交互事件等功能。文章详细讲解了数据模型设计、拖拽功能实现、组件封装及高级扩展(如节点类型定制、连接样式优化等),并提供了性能优化方案以应对大数据量场景。最终,展示了基础网络拓扑、实时更新拓扑等应用实例,为开发者提供了一套完整的实现思路和实践经验。
1915 78
|
JavaScript 前端开发 UED
Vue 手风琴实现的三种常用方式及长尾关键词解析
手风琴效果是Vue开发中常见的交互组件,可节省页面空间、提升用户体验。本文介绍三种实现方式:1) 原生Vue结合数据绑定与CSS动画;2) 使用Element UI等组件库快速构建;3) 自定义指令操作DOM实现独特效果。每种方式适用于不同场景,可根据项目需求选择。示例包括产品特性页、后台菜单及FAQ展示,灵活满足多样需求。附代码示例与资源链接,助你高效实现手风琴功能。
716 10
|
机器学习/深度学习 人工智能 JSON
Resume Matcher:增加面试机会!开源AI简历优化工具,一键解析简历和职位描述并优化
Resume Matcher 是一款开源AI简历优化工具,通过解析简历和职位描述,提取关键词并计算文本相似性,帮助求职者优化简历内容,提升通过自动化筛选系统(ATS)的概率,增加面试机会。
1929 18
Resume Matcher:增加面试机会!开源AI简历优化工具,一键解析简历和职位描述并优化
|
JavaScript 前端开发 UED
Vue 表情包输入组件实现代码及详细开发流程解析
这是一篇关于 Vue 表情包输入组件的使用方法与封装指南的文章。通过安装依赖、全局注册和局部使用,可以快速集成表情包功能到 Vue 项目中。文章还详细介绍了组件的封装实现、高级配置(如自定义表情列表、主题定制、动画效果和懒加载)以及完整集成示例。开发者可根据需求扩展功能,例如 GIF 搜索或自定义表情上传,提升用户体验。资源链接提供进一步学习材料。
936 1
|
JavaScript 前端开发 API
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
658 17
|
负载均衡 网络协议 定位技术
在数字化时代,利用DNS实现地理位置路由成为提升用户体验的有效策略
在数字化时代,利用DNS实现地理位置路由成为提升用户体验的有效策略。通过解析用户请求的来源IP地址,DNS服务器可判断其地理位置,并返回最近或最合适的服务器IP,从而优化网络路由,减少延迟,提高访问速度。示例代码展示了如何基于IP地址判断地理位置并分配相应服务器IP,实际应用中需结合专业地理数据库和动态调整机制,以应对复杂网络环境带来的挑战。
522 6
|
资源调度 JavaScript 前端开发
路由管理:Vue Router的使用和配置技巧
【10月更文挑战第21天】路由管理:Vue Router的使用和配置技巧
439 3
|
存储 JavaScript 前端开发
Vue3权限控制全攻略:路由与组件层面的用户角色与权限管理方法深度解析
Vue3权限控制全攻略:路由与组件层面的用户角色与权限管理方法深度解析
2043 2

推荐镜像

更多
  • DNS