Vue Router 源码探秘:路由匹配的艺术
各位观众,晚上好!今天咱们来聊聊 Vue Router 的核心之一:路由匹配。这玩意儿听起来高大上,其实就是个 “找对象” 的过程,router 负责帮你把 URL 和对应的组件 “配对”。
咱们先从最简单的开始,然后一步步深入到动态路由参数和嵌套路由,最后看看源码里是怎么把这些“配对”逻辑实现的。
1. 路由表:相亲网站
首先,得有个地方存放所有可能的 “对象” 信息,也就是路由配置。Vue Router 用一个路由表来存储这些信息。
// 路由配置示例
const routes = [
{
path: '/',
component: Home
},
{
path: '/about',
component: About
},
{
path: '/user/:id', // 动态路由参数
component: User
},
{
path: '/post',
component: Post,
children: [
{
path: ':postId',
component: PostDetail
}
]
}
]
这个 routes
数组就是我们的“相亲网站”——路由表,每一项都描述了一个路由规则,包括路径 (path
) 和对应的组件 (component
)。
2. 路由匹配:红娘上岗
当用户在浏览器地址栏输入 URL,或者点击 <router-link>
组件时,router 就要开始工作了,它会遍历路由表,找到和当前 URL 匹配的路由规则。这个过程就是路由匹配。
这个过程,可以比作“红娘上岗”,帮助你找到最合适的“对象”。
2.1 精确匹配
最简单的匹配方式是精确匹配,例如:
- 如果 URL 是
/
,那么就匹配path: '/'
的路由。 - 如果 URL 是
/about
,那么就匹配path: '/about'
的路由。
这就像红娘说:“这位小伙子,身高1米8,爱好打篮球,看看有没有符合条件的姑娘?”
2.2 动态路由参数
但是,如果我们需要根据 URL 中的参数来显示不同的内容,比如 /user/123
、/user/456
,怎么办呢? 这时候就要用到动态路由参数。
在路由配置中,我们用 :
开头的字符串来表示动态路由参数,例如:path: '/user/:id'
。
当 URL 是 /user/123
时,router 会把 123
提取出来,作为 id
参数的值,传递给 User
组件。
这就像红娘说:“这位小伙子,喜欢旅行,去过的地方有:北京、上海、广州……,看看有没有喜欢旅行的姑娘,最好也去过这些地方!”
2.3 嵌套路由
有时候,我们需要在同一个页面中显示不同的内容,例如在 Post
组件中显示不同的文章详情。这时候就要用到嵌套路由。
在路由配置中,我们可以使用 children
属性来定义嵌套路由,例如:
{
path: '/post',
component: Post,
children: [
{
path: ':postId',
component: PostDetail
}
]
}
当 URL 是 /post/123
时,router 会先匹配到 path: '/post'
的路由,然后继续在 Post
组件的 children
中查找匹配的路由,最终匹配到 path: ':postId'
的路由。
这就像红娘说:“这位小伙子,条件不错,但是要求也比较高,看看有没有符合条件的姑娘,最好还要符合他父母的要求!”
3. 源码解析:createRouteMatcher
和 resolve
函数
现在,让我们深入到 Vue Router 的源码,看看它是如何实现路由匹配的。
在 Vue Router 的源码中,有两个关键的函数:createRouteMatcher
和 resolve
。
createRouteMatcher
函数负责将路由配置转换为一个路由匹配器,它会将路由配置中的path
转换为正则表达式,方便进行匹配。resolve
函数负责根据当前的 URL 和路由表,找到匹配的路由记录。
3.1 createRouteMatcher
函数
createRouteMatcher
函数主要做以下几件事:
- 扁平化路由表: 将嵌套的路由配置扁平化为一个数组,方便后续的匹配。
- 编译路由路径: 将路由路径转换为正则表达式,用于匹配 URL。
- 创建路由记录: 为每个路由配置创建一个路由记录,包含路由的各种信息,如
path
、component
、meta
等。
下面是 createRouteMatcher
函数的简化版代码:
function createRouteMatcher(routes) {
const routeRecords = [];
function addRoute(route, parent) {
const record = {
path: route.path,
component: route.component,
meta: route.meta || {},
parent,
children: [],
regex: compileRouteRegex(route.path), // 将 path 编译成正则表达式
score: computeScore(route.path, parent) //计算优先级
};
if (route.children) {
route.children.forEach(childRoute => {
addRoute(childRoute, record);
});
}
routeRecords.push(record);
}
routes.forEach(route => {
addRoute(route, null); // parent 为 null 表示根路由
});
return {
resolve: (location) => resolve(location, routeRecords),
};
}
compileRouteRegex
函数负责将路由路径转换为正则表达式,例如:
/about
->/^/about$/
/user/:id
->/^/user/([^/]+)$/
这个正则表达式可以用来匹配 URL,并提取动态路由参数。
computeScore
函数根据路由path和parent路径计算路由的优先级,例如:/user/:id
的优先级比/user/profile
的优先级低,/user/profile
的优先级比/user/
的优先级低。
3.2 resolve
函数
resolve
函数负责根据当前的 URL 和路由表,找到匹配的路由记录。
下面是 resolve
函数的简化版代码:
function resolve(location, routeRecords) {
const matched = [];
for (const record of routeRecords) {
const match = record.regex.exec(location.path); // 使用正则表达式匹配 URL
if (match) {
matched.push({
record,
params: extractParams(match, record), // 提取动态路由参数
});
}
}
if (!matched.length) {
return null; // 没有找到匹配的路由
}
// 找到优先级最高的路由
let bestMatch = matched.sort((a, b) => b.record.score - a.record.score)[0];
const resolved = {
path: location.path,
matched: [],
params: {},
};
let current = bestMatch.record;
while (current) {
resolved.matched.unshift(current);
Object.assign(resolved.params, bestMatch.params);
current = current.parent;
}
return resolved;
}
resolve
函数主要做以下几件事:
- 遍历路由记录: 遍历路由表中的所有路由记录,使用正则表达式匹配 URL。
- 提取动态路由参数: 如果匹配成功,则提取动态路由参数。
- 构建匹配的路由记录: 将匹配的路由记录存储到一个数组中。
- 找到优先级最高的路由:从匹配成功的路由中,找到优先级最高的路由。
- 构建 resolved 对象: 构建一个包含匹配的路由记录、动态路由参数等信息的对象。
extractParams
函数负责从正则表达式的匹配结果中提取动态路由参数,例如:
function extractParams(match, record) {
const params = {};
const keys = record.regex.keys; // 从正则表达式中提取参数名
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
params[key.name] = match[i + 1]; // 从匹配结果中提取参数值
}
return params;
}
4. 例子:URL /user/123
的匹配过程
现在,让我们以 URL /user/123
为例,来看看路由匹配的具体过程。
- 创建路由匹配器:
createRouteMatcher
函数将路由配置转换为一个路由匹配器。 - 调用
resolve
函数:resolve
函数根据 URL/user/123
和路由表,找到匹配的路由记录。 - 遍历路由记录:
resolve
函数遍历路由表中的所有路由记录,使用正则表达式匹配 URL。 - 匹配
path: '/user/:id'
的路由: 当resolve
函数匹配到path: '/user/:id'
的路由时,正则表达式/^/user/([^/]+)$/
匹配成功。 - 提取动态路由参数:
extractParams
函数从正则表达式的匹配结果中提取动态路由参数,得到params: { id: '123' }
。 - 构建 resolved 对象:
resolve
函数构建一个包含匹配的路由记录、动态路由参数等信息的对象,例如:
{
path: '/user/123',
matched: [
{
path: '/user/:id',
component: User,
meta: {},
parent: null,
children: [],
regex: /^/user/([^/]+)$/
}
],
params: {
id: '123'
}
}
5. 总结:路由匹配的艺术
路由匹配是 Vue Router 的核心,它负责将 URL 和对应的组件“配对”。
- 路由表: 存储所有可能的路由规则。
- 精确匹配: 最简单的匹配方式,URL 必须和路由路径完全一致。
- 动态路由参数: 允许在 URL 中包含参数,用于动态显示内容。
- 嵌套路由: 允许在同一个页面中显示不同的内容。
createRouteMatcher
函数: 将路由配置转换为一个路由匹配器。resolve
函数: 根据当前的 URL 和路由表,找到匹配的路由记录。
通过对 Vue Router 源码的分析,我们可以更深入地理解路由匹配的原理,从而更好地使用 Vue Router。
功能 | 描述 | 对应源码关键函数 |
---|---|---|
路由配置 | 定义应用中所有可能的路由规则,包括路径、组件、元数据等。 | routes (配置对象) |
路由匹配 | 根据当前 URL,在路由表中查找匹配的路由记录。 | createRouteMatcher , resolve |
精确匹配 | URL 必须与路由路径完全一致才能匹配成功。 | 使用正则表达式进行全字匹配,例如 /^/about$/ |
动态路由参数 | 在 URL 中使用参数,例如 /user/:id ,可以根据不同的 id 值显示不同的内容。 |
compileRouteRegex (生成带参数捕获的正则表达式), extractParams (提取参数值) |
嵌套路由 | 在组件内部定义子路由,例如 /post/:postId 嵌套在 /post 路由下。 |
addRoute (递归添加子路由), resolve (深度优先搜索匹配路由) |
优先级计算 | 当多个路由都匹配时,根据一定的规则(例如路径长度、静态片段数量)选择优先级最高的路由。 | computeScore (计算路由优先级), sort (对匹配到的路由进行排序) |
路由记录 | 包含路由的所有信息,例如 path 、component 、meta 、regex 等。 |
record (路由记录对象) |
希望今天的分享能帮助大家更好地理解 Vue Router 的路由匹配机制。 谢谢大家!