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

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 在之前的一篇文章中曾经说过,页面的切换其实就是一个找组件的过程,也就是 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
相关文章
|
28天前
|
前端开发 JavaScript
React 步骤条组件 Stepper 深入解析与常见问题
步骤条组件是构建多步骤表单或流程时的有力工具,帮助用户了解进度并导航。本文介绍了在React中实现简单步骤条的方法,包括基本结构、状态管理、样式处理及常见问题解决策略,如状态管理库的使用、自定义Hook的提取和CSS Modules的应用,以确保组件的健壮性和可维护性。
65 17
|
2月前
|
缓存 JavaScript UED
Vue3中v-model在处理自定义组件双向数据绑定时有哪些注意事项?
在使用`v-model`处理自定义组件双向数据绑定时,要仔细考虑各种因素,确保数据的准确传递和更新,同时提供良好的用户体验和代码可维护性。通过合理的设计和注意事项的遵循,能够更好地发挥`v-model`的优势,实现高效的双向数据绑定效果。
154 64
|
1月前
|
前端开发 UED
React 文本区域组件 Textarea:深入解析与优化
本文介绍了 React 中 Textarea 组件的基础用法、常见问题及优化方法,包括状态绑定、初始值设置、样式自定义、性能优化和跨浏览器兼容性处理,并提供了代码案例。
60 8
|
2月前
|
前端开发 JavaScript 测试技术
Vue3中v-model在处理自定义组件双向数据绑定时,如何避免循环引用?
Web 组件化是一种有效的开发方法,可以提高项目的质量、效率和可维护性。在实际项目中,要结合项目的具体情况,合理应用 Web 组件化的理念和技术,实现项目的成功实施和交付。通过不断地探索和实践,将 Web 组件化的优势充分发挥出来,为前端开发领域的发展做出贡献。
45 8
|
2月前
|
JavaScript
在 Vue 3 中,如何使用 v-model 来处理自定义组件的双向数据绑定?
需要注意的是,在实际开发中,根据具体的业务需求和组件设计,可能需要对上述步骤进行适当的调整和优化,以确保双向数据绑定的正确性和稳定性。同时,深入理解 Vue 3 的响应式机制和组件通信原理,将有助于更好地运用 `v-model` 实现自定义组件的双向数据绑定。
|
2月前
|
存储 JavaScript 开发者
Vue 组件间通信的最佳实践
本文总结了 Vue.js 中组件间通信的多种方法,包括 props、事件、Vuex 状态管理等,帮助开发者选择最适合项目需求的通信方式,提高开发效率和代码可维护性。
|
2月前
|
存储 JavaScript
Vue 组件间如何通信
Vue组件间通信是指在Vue应用中,不同组件之间传递数据和事件的方法。常用的方式有:props、自定义事件、$emit、$attrs、$refs、provide/inject、Vuex等。掌握这些方法可以实现父子组件、兄弟组件及跨级组件间的高效通信。
|
2月前
|
缓存 JavaScript UED
Vue 中实现组件的懒加载
【10月更文挑战第23天】组件的懒加载是 Vue 应用中提高性能的重要手段之一。通过合理运用动态导入、路由配置等方式,可以实现组件的按需加载,减少资源浪费,提高应用的响应速度和用户体验。在实际应用中,需要根据具体情况选择合适的懒加载方式,并结合性能优化的其他措施,以打造更高效、更优质的 Vue 应用。
|
2月前
|
前端开发 JavaScript 开发者
揭秘前端高手的秘密武器:深度解析递归组件与动态组件的奥妙,让你代码效率翻倍!
【10月更文挑战第23天】在Web开发中,组件化已成为主流。本文深入探讨了递归组件与动态组件的概念、应用及实现方式。递归组件通过在组件内部调用自身,适用于处理层级结构数据,如菜单和树形控件。动态组件则根据数据变化动态切换组件显示,适用于不同业务逻辑下的组件展示。通过示例,展示了这两种组件的实现方法及其在实际开发中的应用价值。
48 1
|
2月前
|
机器学习/深度学习 自然语言处理 数据管理
GraphRAG核心组件解析:图结构与检索增强生成
【10月更文挑战第28天】在当今数据科学领域,自然语言处理(NLP)和图数据管理技术的发展日新月异。GraphRAG(Graph Retrieval-Augmented Generation)作为一种结合了图结构和检索增强生成的创新方法,已经在多个应用场景中展现出巨大的潜力。作为一名数据科学家,我对GraphRAG的核心组件进行了深入研究,并在此分享我的理解和实践经验。
90 0

推荐镜像

更多
下一篇
开通oss服务