Skip to content

深入解析 Vue Router 源码:全面剖析与实践

引言

Vue Router 是 Vue.js 官方提供的路由管理器,与 Vue.js 核心深度集成,使构建单页面应用(SPA)变得简单高效。通过 Vue Router,开发者可以轻松地在不同的视图组件之间导航,实现复杂的前端路由逻辑。本文将从源码角度深入解析 Vue Router,揭示其内部工作机制。

1. Vue Router 概述

在开始源码分析之前,先简要回顾 Vue Router 的基本概念和使用方式。Vue Router 提供了嵌套路由、动态路由、路由守卫、路由懒加载等丰富的功能,满足现代前端应用的多样化需求。

基本使用示例

import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from './components/Home.vue';
import About from './components/About.vue';
Vue.use(VueRouter);
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
];
const router = new VueRouter({
routes,
});
new Vue({
router,
render: h => h(App),
}).$mount('#app');

上述代码展示了 Vue Router 的基本使用方式,包括路由的定义和 Vue 实例的挂载。

2. 源码目录结构

在深入分析之前,了解 Vue Router 的源码目录结构有助于我们更系统地进行研究。

├── src
│ ├── components
│ │ ├── link.js
│ │ └── view.js
│ ├── history
│ │ ├── abstract.js
│ │ ├── base.js
│ │ ├── hash.js
│ │ └── html5.js
│ ├── util
│ │ ├── path.js
│ │ ├── query.js
│ │ └── ...
│ ├── create-matcher.js
│ ├── create-route-map.js
│ ├── index.js
│ └── install.js
├── package.json
└── README.md

主要目录和文件说明:

  • components/:包含 router-linkrouter-view 组件的实现。
  • history/:实现不同路由模式的历史记录管理,如 Hash 模式和 HTML5 History 模式。
  • util/:工具函数,处理路径、查询参数等。
  • create-matcher.js:创建路由匹配器的逻辑。
  • create-route-map.js:生成路由映射表。
  • index.js:入口文件,定义 VueRouter 类。
  • install.js:提供 Vue 插件的安装方法。

了解这些目录和文件的作用,有助于我们在分析源码时快速定位相关逻辑。

3. VueRouter 类的实现

VueRouter 类是 Vue Router 的核心,负责初始化路由、管理路由状态以及处理导航逻辑。我们从 src/index.js 文件开始,逐步解析 VueRouter 类的实现。

3.1 构造函数

VueRouter 的构造函数中,主要完成以下任务:

  • 合并用户传入的配置项和默认配置。
  • 创建路由匹配器(matcher)。
  • 根据配置的路由模式(mode),实例化对应的历史记录管理器(history)。
function VueRouter(options = {}) {
this.app = null;
this.apps = [];
this.options = options;
this.beforeHooks = [];
this.resolveHooks = [];
this.afterHooks = [];
this.matcher = createMatcher(options.routes || [], this);
let mode = options.mode || 'hash';
this.mode = mode;
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base);
break;
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback);
break;
case 'abstract':
this.history = new AbstractHistory(this, options.base);
break;
default:
if (process.env.NODE_ENV !== 'production') {
assert(false, `invalid mode: ${mode}`);
}
}
}

3.2 createMatcher 函数

createMatcher 函数用于创建路由匹配器,处理路由的匹配和解析。它接受两个参数:路由配置数组和 VueRouter 实例。

function createMatcher(routes, router) {
const { pathList, pathMap, nameMap } = createRouteMap(routes);
function addRoutes(routes) {
createRouteMap(routes, pathList, pathMap, nameMap);
}
function match(raw, currentRoute, redirectedFrom) {
const location = normalizeLocation(raw, currentRoute, false, router);
const { name } = location;
if (name) {
const record = nameMap[name];
if (record) {
const paramNames = record.regex.keys
.filter(key => !key.optional)
.map(key => key.name);
if (typeof location.params !== 'object') {
location.params = {};
}
if (currentRoute && typeof currentRoute.params === 'object') {
for (const key in currentRoute.params) {
if (!(key in location.params) && paramNames.indexOf(key) > -1) {
location.params[key] = currentRoute.params[key];
}
}
}
location.path = fillParams(record.path, location.params, `named route "${name}"`);
return
}
}
return {
route: null,
redirectTo: null,
};
}
return {
match,
addRoutes,
};
}

在前文中,我们对 Vue Router 的基本概念、源码目录结构以及 VueRouter 类的实现进行了初步探讨。接下来,我们将深入分析 Vue Router 的核心模块和机制,包括路由匹配、历史记录管理、导航守卫、组件实现等内容,以全面理解其内部工作原理。

4. 路由匹配机制

路由匹配是 Vue Router 的核心功能之一,它负责根据当前的 URL,找到对应的路由记录,并渲染相应的组件。在 Vue Router 中,路由匹配主要由 createMatcher 函数和 createRouteMap 函数协同完成。

4.1 createRouteMap 函数

createRouteMap 函数的主要作用是根据用户定义的路由配置,生成路由映射表。该函数会遍历所有的路由配置,创建路径列表(pathList)和路径映射(pathMap),以便快速查找和匹配路由。

function createRouteMap(routes, oldPathList, oldPathMap, oldNameMap) {
const pathList = oldPathList || [];
const pathMap = oldPathMap || Object.create(null);
const nameMap = oldNameMap || Object.create(null);
routes.forEach(route => {
addRouteRecord(pathList, pathMap, nameMap, route);
});
return {
pathList,
pathMap,
nameMap
};
}

在上述代码中,addRouteRecord 函数用于将单个路由记录添加到路径列表和路径映射中。该函数会处理嵌套路由、别名等情况,确保所有路由都被正确地记录。

4.2 createMatcher 函数

createMatcher 函数利用 createRouteMap 生成的路由映射表,创建路由匹配器。匹配器提供了 matchaddRoutes 方法,分别用于匹配路由和动态添加路由。

function createMatcher(routes, router) {
const { pathList, pathMap, nameMap } = createRouteMap(routes);
function match(raw, currentRoute, redirectedFrom) {
const location = normalizeLocation(raw, currentRoute, false, router);
const { name } = location;
if (name) {
const record = nameMap[name];
if (record) {
const paramNames = getParamNames(record.path);
location.params = location.params || {};
if (currentRoute && currentRoute.params) {
for (const key in currentRoute.params) {
if (!(key in location.params) && paramNames.includes(key)) {
location.params[key] = currentRoute.params[key];
}
}
}
location.path = fillParams(record.path, location.params, `named route "${name}"`);
return createRoute(record, location, redirectedFrom);
}
} else if (location.path) {
for (const path of pathList) {
const record = pathMap[path];
if (matchRoute(record.regex, location.path, location.params)) {
return createRoute(record, location, redirectedFrom);
}
}
}
return createRoute(null, location);
}
function addRoutes(routes) {
createRouteMap(routes, pathList, pathMap, nameMap);
}
return {
match,
addRoutes
};
}

match 函数中,首先会根据传入的 raw 值(可能是路径或路由名称)规范化为 location 对象。然后,根据 location 中的 namepath,在 nameMappathMap 中查找对应的路由记录。如果找到匹配的记录,则创建并返回一个新的路由对象;否则,返回一个默认的路由对象。

通过上述机制,Vue Router 能够高效地根据当前的 URL,找到对应的路由记录,并为后续的导航和渲染提供支持。

5. 历史记录管理

Vue Router 提供了多种路由模式,包括 hashhistoryabstract。不同的模式对应不同的历史记录管理方式。在 Vue Router 的源码中,history 目录下包含了这些模式的实现。

5.1 BaseHistory 类

BaseHistory 类是所有历史记录管理类的基类,定义了通用的属性和方法。它主要负责管理路由栈、监听路由变化,以及触发导航守卫等。

class BaseHistory {
constructor(router, base) {
this.router = router;
this.base = normalizeBase(base);
this.current = START;
this.pending = null;
this.ready = false;
this.readyCbs = [];
this.readyErrorCbs = [];
this.errorCbs = [];
}
listen(cb) {
this.cb = cb;
}
onReady(cb, errorCb) {
if (this.ready) {
cb();
} else {
this.readyCbs.push(cb);
if (errorCb) {
this.readyErrorCbs.push(errorCb);
}
}
}
onError(errorCb) {
this.errorCbs.push(errorCb);
}
transitionTo(location, onComplete, onAbort) {
const route = this.router.match(location, this.current);
this.confirmTransition(
route,
() => {
this.updateRoute(route);
onComplete && onComplete(route);
this.ensureURL();
this.ready = true;
this.readyCbs.forEach(cb => cb(route));
},
err => {
if (onAbort) {
onAbort(err);
}
if (err && !this.ready) {
this.ready = true;
this.readyErrorCbs.forEach(cb => cb(err));
}
}
);
}
confirmTransition(route, onComplete, onAbort) {
const current = this.current;
const abort = err => {
if (err instanceof Error) {
this.errorCbs.forEach(cb => cb(err));
}
onAbort && onAbort(err);
};
if (isSameRoute(route, current)) {
this.ensureURL();
return abort();
}
const { updated, deactivated } = resolveQueue(
current.matched,
route.matched
);
const queue = [
...deactivated.map(c => {
const { asyncData } = c;
return asyncData && asyncData({
route,
config: c,
router: this.router,
});
}),
...updated.map(m => {
const { asyncData } = m;
return asyncData && asyncData({
route,
config: m,
router: this.router,
});
}),
];
Promise.all(queue).then(onComplete, abort);
}
}

在前文中,我们深入探讨了 Vue Router 的路由匹配机制和历史记录管理。接下来,我们将继续分析 Vue Router 的导航守卫、核心组件(如 router-viewrouter-link)的实现,以及路由懒加载等高级特性,以全面理解其内部工作原理。

6. 导航守卫

导航守卫是 Vue Router 提供的一种路由导航钩子,允许在路由进入或离开之前执行特定的逻辑。这对于权限验证、数据预加载等场景非常有用。Vue Router 提供了三种类型的导航守卫:全局守卫、路由独享守卫和组件内守卫。

6.1 全局守卫

全局守卫是针对整个路由器实例的钩子函数。它们可以通过 router.beforeEachrouter.beforeResolverouter.afterEach 方法进行注册。

  • beforeEach:在每次导航之前调用。
  • beforeResolve:在导航被确认之前,但在所有组件内守卫和异步路由组件被解析之后调用。
  • afterEach:在每次导航完成之后调用。

这些守卫的实现主要依赖于 BaseHistory 类的 confirmTransition 方法。在该方法中,会按照以下顺序执行守卫:

  1. 提取需要离开的组件的 beforeRouteLeave 守卫。
  2. 调用全局的 beforeEach 守卫。
  3. 提取需要更新的组件的 beforeRouteUpdate 守卫。
  4. 调用路由配置中的 beforeEnter 守卫。
  5. 解析异步路由组件。
  6. 提取激活组件的 beforeRouteEnter 守卫。
  7. 调用全局的 beforeResolve 守卫。

在所有异步守卫执行完毕后,导航被确认,然后调用全局的 afterEach 钩子。

6.2 路由独享守卫

路由独享守卫是直接在路由配置中定义的守卫,主要是 beforeEnter。它会在路由进入之前被调用,仅对特定的路由生效。

6.3 组件内守卫

组件内守卫是在组件内部定义的钩子函数,包括:

  • beforeRouteEnter:在路由进入之前调用。
  • beforeRouteUpdate:在路由更新时调用。
  • beforeRouteLeave:在路由离开之前调用。

这些守卫允许组件对路由变化做出响应,执行特定的逻辑。

7. 核心组件实现

Vue Router 提供了两个核心组件:router-viewrouter-link,它们分别用于路由组件的渲染和导航链接的生成。

7.1 router-view

router-view 组件用于渲染匹配到的路由组件。在其实现中,router-view 会根据当前路由的匹配情况,找到对应的组件并渲染。它的实现涉及到对路由记录的解析,以及对嵌套路由的支持。

7.2 router-link

router-link 组件用于生成导航链接。它会根据传入的 to 属性,生成对应的 URL,并在点击时触发路由导航。此外,router-link 还支持设置激活的 CSS 类名,以及处理 replace 等属性。

8. 路由懒加载

为了优化大型应用的性能,Vue Router 支持路由组件的懒加载。这意味着在初始加载时,不会加载所有的路由组件,而是在需要时才进行加载。

实现路由懒加载的方式通常是使用动态导入语法:

const routes = [
{
path: '/foo',
component: () => import('./Foo.vue')
}
];

在上述配置中,component 属性是一个返回 Promise 的函数,只有在访问该路由时,才会执行该函数并加载组件。这有效地减少了初始加载的体积,提高了应用的性能。

9. 总结

通过对 Vue Router 源码的深入分析,我们了解了其路由匹配机制、历史记录管理、导航守卫、核心组件实现以及路由懒加载等核心功能的实现原理。这些机制的巧妙设计,使得 Vue Router 能够高效、灵活地管理前端路由,为开发者提供了强大的工具来构建单页面应用。

深入理解这些原理,不仅有助于我们更好地使用 Vue Router,也为我们在实际项目中定制和优化路由功能提供了理论基础。希望本文能对您有所帮助,助您在前端开发的道路上更进一步。