vue-router 如何找到待渲染的 vue 组件?vue-router Matcher 解析(一)

简介: 在之前的一篇文章中曾经说过,页面的切换其实就是一个找组件的过程,也就是 vue-router 里面的 Matcher, 看过 vue-router4 的源码后,发现 matcher 其实是非常重要的一

背景介绍

在之前的一篇文章中vue-router 如何做到页面切换?, 源码解析曾经说过,页面的切换其实就是一个找组件的过程,也就是 vue-router 里面的 Matcher, 看过 vue-router4 的源码后,发现 matcher 其实是非常重要的一环,可以说是和 history(历史模式) 构成了 vue-router 的两个核心,回过头来看一下 vue-router4 中的创建 router 操作

const router = VueRouter.createRouter({
  history: createWebHistory(),
  routes,
});

其实也暗示着 history(对应历史模式) 和 routes(对应 matcher) 是很重要的,话不多说,本篇文章将会介绍 vue-router4 里面的 matcher

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

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

addRoute

当我们创建 router 实例时,会从 createRouter 将 options.routers 携带到 createRouterMatcher 并在其中初始化 matcher,代码如下

// code
const routes = [
  { path: "/", component: Home },
  { path: "/about", component: About },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});
// source code
function createRouter(options: RouterOptions): Router {
  // options.routes 即 routes = [{ path: "/",... },{ path: "/about",...}]
  const matcher = createRouterMatcher(options.routes, options);
  // ...
}

重点在 createRouterMatcher 里面的 addRoute,注意是一条条 route 处理的,整个 matcher 中的封装操作将在里面完成,而 route 的解析将告一段落,至于封装的意思就是,刚开始传进去的 routes 将不会是 vue-router4 中真正意义上使用的 routes,matcher 部分会将其进一步处理,像别名这些都要进一步处理

function createRouterMatcher(
  routes: RouteRecordRaw[],
  globalOptions: PathParserOptions
): RouterMatcher {
  // ...
  // 添加初始化后的 routes, 或者说封装后的 routes
  routes.forEach((route) => addRoute(route));
  // ...
}

addRoute 其实在 createRouterMatcher 里面做了下面 5 件事

  1. 初步修缮 routes - normalize
  2. 合并 option
  3. 处理别名 - alia
  4. 第二次修缮 routes
  5. 返回 真 - routes

接下来就针对以上几点去介绍

初步修缮 routes - normalize

这个地方主要是负责路由组件传参这块的内容,具体功能可以看 vue-router4 的文档,而文章这里将会介绍它是怎么做的,为什么要这么做

matcher 在 normalize 这里入口是一个工厂函数(小细节了啊),但是我们要知道 vue-router4 是用 typescript 写的,它的返回值也是有定义的,让我们看看这个函数

function normalizeRouteRecord(record: RouteRecordRaw): RouteRecordNormalized {
  return {
    path: record.path,
    redirect: record.redirect,
    // ...
  };
}

它返回值是一个叫 RouteRecordNormalized 的接口 interface, 它的这个工厂函数里面就是返回一个键与 RouteRecordNormalized 相对应的一个对象,不知道这么做是不是有让读者更方便去看给 RouteRecordNormalized 中的哪个属性如何赋值的意思,像在 java 里面很有可能就是 RouteRecordNormalized(path, redirect, ...) 构造函数创建了,当然前提是 RouteRecordNormalized 得是个 Class(不知道用 ts 的人用 Class 的多不多?)

那么这个 RouteRecordNormalized 的目的就是返回处理后的 copy 值(又是 JS 老生常谈的对象引用值问题),这个时候我们原先设定好的 route 在这里就进行了第一步的处理

// code
const routes = [{ path: "/", component: Home }];
// source code
// route: RouteRecordRaw to route: RouteRecordNormalized

那么这个工厂函数它对我们一开始传进来的 route 做了什么处理呢?接下来就是处理过程

处理过程

上面这两个类型在 vue-router4 的 API 参考文档里面有,处理过程就是对 RouteRecordRaw to RouteRecordNormalized 赋初值以及新增属性,下面是处理前后的 route 比较

// code
const route = { path: "/", component: Home };
// 对应下面的
const route = {
  path: "/",
  redirect: undefined,
  name: undefined,
  meta: undefined,
  alias: undefined,
  beforeEnter: undefined,
  props: undefined,
  children: undefined,
};
// source code: normalizeRouteRecord 处理后
const route = {
  path: "/",
  redirect: undefined,
  name: undefined,
  meta: {},
  beforeEnter: undefined,
  props: undefined,
  children: [],
  // 新增部分
  aliasOf: undefined,
  instances: {},
  leaveGuards: new Set(),
  updateGuards: new Set(),
  enterCallbacks: {},
  components: undefined,
};

这个处理过程比较重要的一个点就是 props 的处理,这里可以看一下 vue-router-API-props 参考, 文档里面的要求可以在源码这里体现出来,简简单单来欣赏一下这段源码

function normalizeRecordProps(
  record: RouteRecordRaw
): Record<string, _RouteRecordProps> {
  const propsObject = {} as Record<string, _RouteRecordProps>;
  // 重定向的路由不会有 props, 因此其 props 值应为 false
  const props =
    (record as Exclude<RouteRecordRaw, RouteRecordRedirect>).props || false;
  if ("component" in record) {
    propsObject.default = props;
  } else {
    // 小细节: props 函数模式能应用到对应的命名视图组件里面
    for (const name in record.components)
      propsObject[name] = typeof props === "boolean" ? props : props[name];
  }

  return propsObject;
}

上面这段源码其实就是针对了 props 的三种模式

  1. 布尔模式
  2. 对象模式
  3. 函数模式

在 props 的获取中,对于重定向路由做了特殊处理,默认设置为 false, 但是如果手贱设置了 props 属性,不知道会不会对 vue-router 造成很大的影响?毕竟 vue-router4 其实对 route 的值的约束其实很小

route 指 { path: "/", Redirect: "/test" }

// code
{ path: "/", Redirect: "/test" }
// source code
{ path: "/", Redirect: "/test", props: false }

对象模式和布尔模式是这样处理的

if ('component' in record) {
  propsObject.default = props

这里我们要注意一个小细节, 是设定的 propsObject.default = props, 为什么是 default ? 因为要兼容命名视图,所以无论你一开始的 route 是否是命名视图模式,处理过后的 props 一定有 propsObject.default = xxx

又是一个小细节

命名视图就不用说了,看一下处理后会是怎么样

for (const name in record.components)
  propsObject[name] = typeof props === "boolean" ? props : props[name];
// code
const props = { default: true, other: true }
// source code
const propsObject = { default: true, other: true }

vue-router 文档中没有的小细节

从上面源码中其实我们可以看到,当 route 是命名视图模式时,如果 props 是 boolean 实际上会被应用到每个组件上,比如下面的例子

const routes = [
  {
    path: '/user/:id',
    components: { default: User, sidebar: Sidebar },
    props: true
  }
]
// 等于下面这个
const routes = [
  {
    path: '/user/:id',
    components: { default: User, sidebar: Sidebar },
    props: { default: true, sidebar: true }
  }
]

总结

vue-router 的 matcher

  1. createRouterMatcher - 创建 matcher 并初始化
  2. addRoute - 初始化传入的 route
  3. normalizeRouteRecord - 封装传入的 route, 相当于升级,对应 API 文档的 RouteRecordNormalized
  4. normalizeRecordProps - 处理 props,对应文档的路由传参部分

结尾也回归主题一下吧,想要找到组件那你找不就完了?那如果你的目的是这个,那看到 addRoute 那里你就可以结束 matcher 的部分了,那 addRoute 了,内存就有一个路由映射表了,剩下的就找不就完事了?但文章其实要讲的更加细节(没错,是细节怪)找到正确的路由才算成功,那路由的处理也算是一部分

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

  1. vue-router 如何找到待渲染的 vue 组件?vue-router Matcher 解析(二)- 没写 - 处理别名
  2. vue-router 如何找到待渲染的 vue 组件?vue-router Matcher 解析(三)- 没写 - 第二次处理 routes
相关文章
|
10月前
|
JavaScript
Vue中如何实现兄弟组件之间的通信
在Vue中,兄弟组件可通过父组件中转、事件总线、Vuex/Pinia或provide/inject实现通信。小型项目推荐父组件中转或事件总线,大型项目建议使用Pinia等状态管理工具,确保数据流清晰可控,避免内存泄漏。
794 2
|
人工智能 JavaScript 算法
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
1362 0
|
JavaScript UED
用组件懒加载优化Vue应用性能
用组件懒加载优化Vue应用性能
|
JavaScript 前端开发 UED
Vue 表情包输入组件实现代码及详细开发流程解析
这是一篇关于 Vue 表情包输入组件的使用方法与封装指南的文章。通过安装依赖、全局注册和局部使用,可以快速集成表情包功能到 Vue 项目中。文章还详细介绍了组件的封装实现、高级配置(如自定义表情列表、主题定制、动画效果和懒加载)以及完整集成示例。开发者可根据需求扩展功能,例如 GIF 搜索或自定义表情上传,提升用户体验。资源链接提供进一步学习材料。
875 1
|
JavaScript 前端开发 UED
Vue 项目中如何自定义实用的进度条组件
本文介绍了如何使用Vue.js创建一个灵活多样的自定义进度条组件。该组件可接受进度段数据数组作为输入,动态渲染进度段,支持动画效果和内容展示。当进度超出总长时,超出部分将以红色填充。文章详细描述了组件的设计目标、实现步骤(包括props定义、宽度计算、模板渲染、动画处理及超出部分的显示),并提供了使用示例。通过此组件,开发者可根据项目需求灵活展示进度情况,优化用户体验。资源地址:[https://pan.quark.cn/s/35324205c62b](https://pan.quark.cn/s/35324205c62b)。
609 0
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
572 2
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
941 140
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
|
算法 测试技术 C语言
深入理解HTTP/2:nghttp2库源码解析及客户端实现示例
通过解析nghttp2库的源码和实现一个简单的HTTP/2客户端示例,本文详细介绍了HTTP/2的关键特性和nghttp2的核心实现。了解这些内容可以帮助开发者更好地理解HTTP/2协议,提高Web应用的性能和用户体验。对于实际开发中的应用,可以根据需要进一步优化和扩展代码,以满足具体需求。
1534 29
|
前端开发 数据安全/隐私保护 CDN
二次元聚合短视频解析去水印系统源码
二次元聚合短视频解析去水印系统源码
612 4
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~

热门文章

最新文章

推荐镜像

更多
  • DNS