这是对vue-router 3 版本的源码分析。
本次分析会按以下方法进行:
- 按官网的使用文档顺序,围绕着某一功能点进行分析。这样不仅能学习优秀的项目源码,更能加深对项目的某个功能是如何实现的理解。这个对自己的技能提升,甚至面试时的回答都非常有帮助。
- 在围绕某个功能展开讲解时,所有不相干的内容都会暂时去掉,等后续涉及到对应的功能时再加上。这样最大的好处就是能循序渐进地学习,同时也不会被不相干的内容影响。省略的内容都会在代码中以…表示。
- 每段代码的开头都会说明它所在的文件目录,方便定位和查阅。如果一个函数内容有多个函数引用,这些都会放在同一个代码块中进行分析,不同路径的内容会在其头部加上所在的文件目录。
本章讲解router中重定向是如何实现的。
另外我的vuex3源码分析也发布完了,欢迎大家学习:
vuex3 最全面最透彻的源码分析
还有vue-router的源码分析:
vue-router 源码分析——1. 路由匹配
vue-router 源码分析——2. router-link 组件是如何实现导航的
vue-router 源码分析——3. 动态路由匹配
vue-router 源码分析——4.嵌套路由
vue-router 源码分析——5.编程式导航
vue-router 源码分析——6.命名路由
vue-router 源码分析——7.命名视图
官方示例:
- 重定向是通过 routes 配置来完成,重定向的目标也可以是一个命名路由,或者一个方法。
const router = new VueRouter({
routes: [
{ path: '/a', redirect: '/b' },
{ path: '/b', component: b_component} // 重定向的路由必须在router中定义
]
})
const router = new VueRouter({
routes: [
{ path: '/a', redirect: { name: 'foo' }}
]
})
const router = new VueRouter({
routes: [
{ path: '/a', redirect: to => {
// 方法接收 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
}}
]
})
Router初始化:
- Router初始化生成匹配器和路由记录表,redirect属性会被复制到路由的路由记录上:
// router.js
import type { Matcher } from './create-matcher'
export default class VueRouter {
...
constructor(options: RouterOptions = {}) {
...
this.matcher = createMatcher(options.routes || [], this)
}
}
// ./create-matcher.js
import { createRouteMap } from './create-route-map'
export function createMatcher(
routes: Array<RouteConfig>,
router: VueRouter
): Matcher {
const { pathList, pathMap, nameMap } = createRouteMap(routes)
...
}
// ./create-route-map/js
export function createRouteMap(
routes: Array<RouteConfig>,
...
): {
pathList: Array<string>,
pathMap: Dictionary<RouteRecord>,
nameMap: Dictionary<RouteRecord>
} {
const pathList: Array<string> = []
const pathMap: Dictionary<RouteRecord> = Object.create(null)
const nameMap: Dictionary<RouteRecord> = Object.create(null)
routes.forEach(route => {
addRouteRecord(pathList, pathMap, nameMap, route, parentRoute)
})
...
}
function addRouteRecord(
pathList: Array<string>,
pathMap: Dictionary<RouteRecord>,
nameMap: Dictionary<RouteRecord>,
route: RouteConfig,
parent?: RouteRecord,
matchAs?: string
) {
...
const record: RouteRecord = {
redirect: route.redirect,
...
}
}
触发逻辑:
- 当试图跳转到url ‘/a’ 时,会触发路由匹配。
- 首先匹配到路由路径为’/a’的路由记录,然后判断它是否有redirect属性,如果有就执行redirect函数。
- 接着在redirect函数中取出redirect内容,对其进行了结构类型统一化。这里就解释了为什么配置重定向的目标可以是字符串形式的path,或者命名路由,或者方法。
- 最后,构建了一个基于redirect目标的数据,作为参数传入match函数进行路由匹配。这样的递归调用也能解决多重的重定向。
// create-matcher.js
export function createMatcher(
routes: Array<RouteConfig>,
router: VueRouter
): Matcher {
const { pathList, pathMap, nameMap } = createRouteMap(routes)
...
function match(
raw: RawLocation,
currentRoute?: Route,
redirectedFrom?: Location
): Route {
const location = normalizeLocation(raw, currentRoute, false, router)
const { name } = location
if (name) {
// 基于命名路由的匹配逻辑
} else if (location.path) {
location.params = {}
for (let i = 0; i < pathList.length; i++) {
const path = pathList[i]
const record = pathMap[path]
if (matchRoute(record.regex, location.path, location.params)) {
// 这里匹配到路由'/a'的相关记录后,执行创建路由的操作
return _createRoute(record, location, redirectedFrom)
}
}
}
}
function _createRoute(
record: ?RouteRecord,
location: Location,
redirectedFrom?: Location
): Route {
// 如果当前的路由记录有重定向,就执行重定向的相关逻辑,否则创建'/a'的路由内容
if (record && record.redirect) {
return redirect(record, redirectedFrom || location)
}
...
return createRoute(record, location, redirectedFrom, router)
}
function redirect(
record: RouteRecord,
location: Location
): Route {
const originalRedirect = record.redirect
// 下面这段代码解释了为什么重定向可以是一个方法,同时接受的参数是目标路由,
let redirect = typeof originalRedirect === 'function'
? originalRedirect(createRoute(record, location, null, router))
: originalRedirect
// 最后无论重定向设置的是字符串,命名路由,方法,都会统一成一个对象结构
if (typeof redirect === 'string') {
redirect = {path = redirect}
}
...
const re: Object = redirect
const { name, path } = re
...
if (name) {
const targetRecord = nameMap[name]
...
return match({
name,
...
}, undefined, location)
} else if (path) {
const rawPath = resolveRecordPath(path, record)
const resolvedPath = fillParams(rawPath, params, `redirect route with path "${rawPath}"`)
return match({
path: resolvedPath, // 官网示例中定义的重定向都不复杂,这里的path = '/b'没有任何改变
...
}, undefined, location)
}
}
}
细节补充:
- 当重定向完成,匹配路由成功后,调用createRoute函数生成匹配路由时,增加了一个redirectedFrom属性,记录的是它的重定向的源路径。这样可以方便用户查看或者执行其他功能。
// ./util/route.js
export function createRoute(
record: ?RouteRecord,
location: Location,
redirectedFrom?: ?Location,
router?: VueRouter
): Route {
...
const route: Route = {
...
}
if (redirectedFrom) {
route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery)
}
return Object.freeze(route)
}