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

本文涉及的产品
云解析DNS,个人版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 在之前的一篇文章中曾经说过,页面的切换其实就是一个找组件的过程,也就是 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
相关文章
|
1月前
|
缓存 安全 PHP
【PHP开发专栏】Symfony框架核心组件解析
【4月更文挑战第30天】本文介绍了Symfony框架,一个模块化且高性能的PHP框架,以其可扩展性和灵活性备受开发者青睐。文章分为三部分,首先概述了Symfony的历史、特点和版本。接着,详细解析了HttpFoundation(处理HTTP请求和响应)、Routing(映射HTTP请求到控制器)、DependencyInjection(管理依赖关系)、EventDispatcher(实现事件驱动编程)以及Security(处理安全和认证)等核心组件。
|
10天前
|
JavaScript 前端开发 搜索推荐
CSR、SSR与同构渲染全方位解析
CSR、SSR与同构渲染全方位解析
16 0
|
2天前
|
JavaScript 前端开发 API
Vue核心指令解析:探索MVVM与数据操作之美
Vue核心指令解析:探索MVVM与数据操作之美
|
2天前
|
存储 缓存 JavaScript
【前端 - Vue】之 Keep-Alive缓存组件使用语法及原理解析,超详细!
【前端 - Vue】之 Keep-Alive缓存组件使用语法及原理解析,超详细!
|
15天前
|
Kubernetes 应用服务中间件 API
Kubernetes关键组件解析
【6月更文挑战第12天】Kubernetes是一个用于管理容器集群的平台,由Master节点负责集群控制,而Node节点执行管理命令。
|
29天前
|
存储 缓存 监控
深度解析操作系统中的核心组件:进程管理与内存优化
【5月更文挑战第29天】 在现代计算技术的心脏,操作系统扮演着至关重要的角色。它不仅管理和控制计算机硬件资源,还为应用程序提供了一个运行环境。本文将深入探讨操作系统中的两个核心组件——进程管理和内存管理,并分析它们对系统性能的影响以及如何通过技术手段实现优化。通过对操作系统内部机制的剖析,我们将揭示这些组件是如何相互作用,以及它们如何共同提升系统的响应速度和稳定性。
|
1月前
|
JavaScript 前端开发 算法
vue生命周期函数原理解析,vue阻止事件冒泡方法实现
vue生命周期函数原理解析,vue阻止事件冒泡方法实现
|
7天前
|
机器学习/深度学习 缓存 算法
netty源码解解析(4.0)-25 ByteBuf内存池:PoolArena-PoolChunk
netty源码解解析(4.0)-25 ByteBuf内存池:PoolArena-PoolChunk
|
9天前
|
XML Java 数据格式
深度解析 Spring 源码:从 BeanDefinition 源码探索 Bean 的本质
深度解析 Spring 源码:从 BeanDefinition 源码探索 Bean 的本质
20 3
|
1天前
|
Java 数据库连接 Spring
Spring 整合 MyBatis 底层源码解析
Spring 整合 MyBatis 底层源码解析

推荐镜像

更多