Appearance
Vue-Router 源码摘要
初始化安装 install
ts
const router: Router = {
install(app: App) {
const router = this
// 注册全局组件
app.component('RouterLink', RouterLink)
app.component('RouterView', RouterView)
app.config.globalProperties.$router = router
Object.defineProperty(app.config.globalProperties, '$route', {
enumerable: true,
get: () => unref(currentRoute),
})
const reactiveRoute = {}
for (const key in START_LOCATION_NORMALIZED) {
reactiveRoute[key] = computed(() => currentRoute.value[key])
}
// 数据注入
app.provide(..., ...)
// 卸载
const unmountApp = app.unmount
installedApps.add(app)
app.unmount = function () {
installedApps.delete(app)
unmountApp()
}
}
}
return router创建路由 createRouter
ts
createRouter(options) {
const matcher = createRouterMatcher(options.routes, options) {
// 优化排序后的匹配项数组
const matchers: RouteRecordMatcher[] = []
const matcherMap = new Map()
function getRecordMatcher(name: RouteRecordName) {
return matcherMap.get(name)
}
function addRoute(...) { /* createRouterMatcher/addRoute */ }
routes.forEach(route => addRoute(route))
return { addRoute, resolve, removeRoute, getRoutes, getRecordMatcher }
}
const routerHistory = options.history
const beforeGuards = useCallbacks<NavigationGuardWithThis<undefined>>()
const beforeResolveGuards = useCallbacks<NavigationGuardWithThis<undefined>>()
const afterGuards = useCallbacks<NavigationHookAfter>()
const currentRoute = shallowRef<RouteLocationNormalizedLoaded>(
START_LOCATION_NORMALIZED
)
// 改变路由
function push(to: RouteLocationRaw) {
return pushWithRedirect(to)
}
function pushWithRedirect(to, redirectedFrom) {
const targetLocation: RouteLocation = (pendingLocation = resolve(to))
const shouldRedirect = handleRedirectRecord(targetLocation)
if (shouldRedirect) pushWithRedirect(...)
let failure: NavigationFailure | void | undefined
return (failure ? Promise.resolve(failure) : navigate(toLocation, from)).then(() => {
// 执行 全局钩子 afterEach
triggerAfterEach(to, from, failure) {
for (const guard of afterGuards.list()) guard(to, from, failure)
}
})
}
const go = (delta: number) => routerHistory.go(delta)
const installedApps = new Set<App>()
const router: Router = {
currentRoute,
addRoute,
beforeEach: beforeGuards.add,
beforeResolve: beforeResolveGuards.add,
afterEach: afterGuards.add,
install(app: App) { /* 如下 */}
}
return router
}添加匹配 createRouterMatcher/addRoute
ts
function addRoute(
record: RouteRecordRaw,
parent?: RouteRecordMatcher,
originalRecord?: RouteRecordMatcher
) {
const mainNormalizedRecord = normalizeRouteRecord(record) {
return {
path: record.path,
redirect: record.redirect,
name: record.name,
meta: record.meta || {},
...
}
}
const normalizedRecords = [mainNormalizedRecord]
let matcher: RouteRecordMatcher
for (const normalizedRecord of normalizedRecords) {
matcher = createRouteRecordMatcher(normalizedRecord, parent, options) {
// normalizedRecord => record
const parser = tokensToParser(tokenizePath(record.path), options)
const matcher: RouteRecordMatcher = assign(parser, {
record,
parent,
children: [],
alias: [],
})
return matcher
}
if (mainNormalizedRecord.children) {
const children = mainNormalizedRecord.children
for (let i = 0; i < children.length; i++) {
addRoute(
children[i],
matcher,
originalRecord && originalRecord.children[i]
)
}
}
originalRecord = originalRecord || matcher
if(...) {
insertMatcher(matcher) {
let i = 0
while(i < matchers.length && ...) {
i++
}
matchers.splice(i, 0, matcher)
// 缓存记录
if (matcher.record.name && !isAliasRecord(matcher)) {
matcherMap.set(matcher.record.name, matcher)
}
}
}
}
}触发导航、执行守卫钩子 navigate
钩子分为 3 种:
- 全局钩子:beforeEach、beforeResolve、afterEach
- 配置文件内钩子:beforeEnter
- 组件内钩子:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
钩子执行顺序示意图:
导航push=>pushWithRedirect=>navigate=>失活组件beforeRouteLeave=>全局beforeEach=>重用组件beforeRouteUpdate=>路由文件beforeEnter=>解析异步路由组件=>激活组件beforeRouteEnter=>全局beforeResolve=>导航被确认=>全局afterEach=>触发DOM更新=>beforeRouteEnter中回调函数 next(vm)
ts
function navigate(to, from) {
let guards: Lazy<any>[];
const [leavingRecords, updatingRecords, enteringRecords] =
extractChangingRecords(to, from);
// 提取 组件内钩子 beforeRouteLeave
guards = extractComponentsGuards(
leavingRecords.reverse(),
"beforeRouteLeave",
to,
from
);
// 提取 setup 状态下钩子 beforeRouteLeave
for (const record of leavingRecords) {
record.leaveGuards.forEach((guard) => {
guards.push(guardToPromiseFn(guard, to, from));
});
}
// 导航取消确认 机制
const canceledNavigationCheck = checkCanceledNavigationAndReject.bind(
null,
to,
from
);
guards.push(canceledNavigationCheck);
return runGuardQueue(guards)
.then(() => {
// 解析、执行 全局钩子 beforeEach
guards = [];
for (const guard of beforeGuards.list()) {
guards.push(guardToPromiseFn(guard, to, from));
}
guards.push(canceledNavigationCheck);
return runGuardQueue(guards);
})
.then(() => {
// 解析、执行重用 组件内钩子 beforeRouteUpdate
})
.then(() => {
// 解析、执行 配置文件钩子 beforeEnter
guards = [];
for (const record of to.matched) {
if (record.beforeEnter && !from.matched.includes(record)) {
if (isArray(record.beforeEnter)) {
for (const beforeEnter of record.beforeEnter)
guards.push(guardToPromiseFn(beforeEnter, to, from));
} else {
guards.push(guardToPromiseFn(record.beforeEnter, to, from));
}
}
}
guards.push(canceledNavigationCheck);
return runGuardQueue(guards);
})
.then(() => {
// 解析、执行 组件内钩子 beforeRouteEnter
})
.then(() => {
// 解析、执行 全局钩子 beforeResolve
});
}
function runGuardQueue(guards: Lazy<any>[]): Promise<void> {
return guards.reduce(
(promise, guard) => promise.then(() => guard()),
Promise.resolve()
);
}路由视图 RouterView
ts
export const RouterView = RouterViewImpl;
export const RouterViewImpl = /*#__PURE__*/ defineComponent({
name: "RouterView",
props: {
name: {
type: String as PropType<string>,
default: "default",
},
route: Object as PropType<RouteLocationNormalizedLoaded>,
},
setup(props, { attrs, slots }) {
// 获取当前路由及匹配项
const injectedRoute = inject(routerViewLocationKey)!;
const routeToDisplay = computed<RouteLocationNormalizedLoaded>(
() => props.route || injectedRoute.value
);
const injectedDepth = inject(viewDepthKey, 0);
const matchedRouteRef = computed<RouteLocationMatched | undefined>(
() => routeToDisplay.value.matched[depth.value]
);
// 返回值 为RouterView组件 render 函数
return () => {
// 当前路由
const route = routeToDisplay.value;
// 提取 命中路由记录中对应名字视图里的 对应组件
const currentName = props.name;
const matchedRoute = matchedRouteRef.value;
const ViewComponent =
matchedRoute && matchedRoute.components![currentName];
// 创建 vnode
const component = h(
ViewComponent,
assign({}, routeProps, attrs, {
onVnodeUnmounted,
ref: viewRef,
})
);
return component;
};
},
});辅助信息集锦
ts
export const START_LOCATION_NORMALIZED: RouteLocationNormalizedLoaded = {
path: "/",
name: undefined,
params: {},
query: {},
hash: "",
fullPath: "/",
matched: [],
meta: {},
redirectedFrom: undefined,
};