简介
路由的概念相信大部分同学并不陌生,我们在用 Vue
开发过实际项目的时候都会用到 Vue-Router
这个官方插件来帮我们解决路由的问题。它的作用就是根据不同的路径映射到不同的视图。本文不再讲述路由的基础使用和API
,不清楚的同学可以自行查阅官方文档vue-router3 对应 vue2 和 vue-router4 对应 vue3。今天我们从源码出发以vue-router 3.5.3
源码为例,一起来分析下Vue-Router
的具体实现。
由于篇幅原因,
vue-router
源码分析分上、中、下三篇文章讲解。
在上文中我者讲述路路由的一些前置知识,以及路由安装模块的分析。今天我们来讲讲路由的实例化到底都干了些什么事情。
实例化
实例化就是我们new VueRouter({routes})
的过程,我们来重点分析下VueRouter
的构造函数。
分析VueRouter构造函数
VueRouter
的构造函数在src/index.js
中。
// index.js
constructor (options: RouterOptions = {}) {
if (process.env.NODE_ENV !== 'production') {
warn(this instanceof VueRouter, `Router must be called with the new operator.`)
}
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.fallback =
mode === 'history' && !supportsPushState && options.fallback !== false
if (this.fallback) {
mode = 'hash'
}
if (!inBrowser) {
mode = 'abstract'
}
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}`)
}
}
}
构造函数参数
构造函数接收RouterOptions
参数,也就是我们new VueRouter({})
传递的参数。
具体参数意思如下:
export interface RouterOptions {
routes?: RouteConfig[] // 路由配置规则列表
mode?: RouterMode // 模式
fallback?: boolean // 是否启用回退到hash模式
base?: string // 路由base url
linkActiveClass?: string // router-link激活时类名
linkExactActiveClass?: string // router-link精准激活时类名
parseQuery?: (query: string) => Object // 自定义解析qs的方法
stringifyQuery?: (query: Object) => string // 自定义序列化qs的方法
scrollBehavior?: ( // 控制滚动行为
to: Route,
from: Route,
savedPosition: Position | void ) => PositionResult | Promise<PositionResult> | undefined | null
}
参数初始化
我们看到在最开始有些参数的初始化,这些参数到底是什么呢?
this.app
用来保存根 Vue
实例。
this.apps
用来保存持有 $options.router
属性的 Vue
实例。
this.options
保存传入的路由配置。
this.beforeHooks
、 this.resolveHooks
、this.afterHooks
表示一些钩子函数,我们之后会介绍。
this.fallback
表示在浏览器不支持 history.pushState
的情况下,根据传入的 fallback
配置参数,决定是否回退到hash
模式。
this.mode
表示路由创建的模式。
创建matcher
通过createMatcher(options.routes || [], this)
生成matcher
,这个matcher
对象就是前面聊的匹配器,负责url
匹配,它接收了routes
和router实例
。
这个非常重要,我们来重点分析。
分析create-matcher
// src/create-matcher.js
...
// Matcher数据结构,包含四个方法
export type Matcher = {
match: (raw: RawLocation, current?: Route, redirectedFrom?: Location) => Route;
addRoutes: (routes: Array<RouteConfig>) => void;
addRoute: (parentNameOrRoute: string | RouteConfig, route?: RouteConfig) => void;
getRoutes: () => Array<RouteRecord>;
};
// createMatcher返回Matcher对象
export function createMatcher ( routes: Array<RouteConfig>, // 路由配置列表
router: VueRouter // VueRouter实例 ): Matcher {
// 创建路由映射表
const { pathList, pathMap, nameMap } = createRouteMap(routes)
// 批量添加路由
function addRoutes (routes) {
// 所以这里会重新调用createRouteMap方法
createRouteMap(routes, pathList, pathMap, nameMap)
}
// 添加单个路由
function addRoute (parentOrRoute, route) {
...
}
// 获取路由关系数组
function getRoutes () {
return pathList.map(path => pathMap[path])
}
// 传入Location和Route,返回匹配的Route对象
function match ( raw: RawLocation,
currentRoute?: Route,
redirectedFrom?: Location ): Route {
...
}
return {
match,
addRoute,
getRoutes,
addRoutes
}
...
}
matcher
对象不光定义了match、addRoute、addRoutes、getRoutes
四个方法供我们调用,而且在最开始通过createRouteMap()
方法创建了路由映射表RouteMap
。
这里的match
方法非常重要,后面我们会分析。
下面我们来看看createRouteMap()
方法。
分析createRouteMap
// src/create-route-map.js
// 创建路由映射map、添加路由记录
export function createRouteMap ( routes: Array<RouteConfig>, // 路由配置列表
oldPathList?: Array<string>, // 旧pathList
oldPathMap?: Dictionary<RouteRecord>, // 旧pathMap
oldNameMap?: Dictionary<RouteRecord>// 旧nameMap ): {
pathList: Array<string>,
pathMap: Dictionary<RouteRecord>,
nameMap: Dictionary<RouteRecord>
} {
// 若旧的路由相关映射列表及map存在,则使用旧的初始化(借此实现添加路由功能)
// the path list is used to control path matching priority
const pathList: Array<string> = oldPathList || []
// $flow-disable-line
const pathMap: Dictionary<RouteRecord> = oldPathMap || Object.create(null)
// $flow-disable-line
const nameMap: Dictionary<RouteRecord> = oldNameMap || Object.create(null)
// 遍历路由配置对象,生成/添加路由记录
routes.forEach(route => {
addRouteRecord(pathList, pathMap, nameMap, route)
})
// 确保path:*永远在在最后
for (let i = 0, l = pathList.length; i < l; i++) {
if (pathList[i] === '*') {
pathList.push(pathList.splice(i, 1)[0])
l--
i--
}
}
// 开发环境,提示非嵌套路由的path必须以/或者*开头
if (process.env.NODE_ENV === 'development') {
// warn if routes do not include leading slashes
const found = pathList
// check for missing leading slash
.