前言
【vue-router源码】系列文章将带你从0开始理解vue-router
的具体实现。该系列文章源码参考vue-router v4.0.15
。
源码地址:https://github.com/vuejs/router
浏览该文章的前提是你最好理解vue-router
的根本应用,如果你没有应用过的话,可通过vue-router官网学习下。
本篇文章将介绍router.go
、router.back
、router.forward
的实现。
应用
go
函数容许你在历史中后退或后退,指定步数如果>0
,代表后退;<0
代表后退。
router.go(-2)router.back()// 等同于router.go(-1)router.forward()// 等同于router.go(1)
go
go
接管一个参数delta
,示意绝对以后页面,挪动多少步,正数示意后退,负数示意后退。
const go = (delta: number) => routerHistory.go(delta)
在routerHistory.go
中会调用history.go
,进而触发popstate
监听函数。如果你看过之前createWebHistory
的解析,你会晓得在createWebHistory
中通过useHistoryListeners
创立historyListeners
时,会注册一个popstate
监听函数,这个监听函数在调用history.go
后就会触发。
// 文件地位:src/history/html5.ts useHistoryListeners办法window.addEventListener('popstate', popStateHandler)const popStateHandler: PopStateListener = ({ state,}: { state: StateEntry | null}) => { // 以后location,字符串 const to = createCurrentLocation(base, location) const from: HistoryLocation = currentLocation.value const fromState: StateEntry = historyState.value let delta = 0 // 如果不存在state // 对于为什么state可能为空,可参考:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/popstate_event if (state) { currentLocation.value = to historyState.value = state // 如果暂停监听了,并且暂停时的状态是from,间接return if (pauseState && pauseState === from) { pauseState = null return } // 计算挪动的步数 delta = fromState ? state.position - fromState.position : 0 } else { replace(to) } // 循环调用监听函数 listeners.forEach(listener => { listener(currentLocation.value, from, { delta, type: NavigationType.pop, direction: delta ? delta > 0 ? NavigationDirection.forward : NavigationDirection.back : NavigationDirection.unknown, }) })}
能够看到,在监听函数最初会循环调用listeners
中的listener
,那么listener
是什么?什么时候被增加的呢?
在后面文章介绍install
的实现时,其中有一步十分重要的操作就是要依据地址栏的url进行第一次跳转。而个跳转是通过调用push
办法实现的,因为push
会调用pushWidthRedirect
办法,在pushWidthRedirect
中的最初会执行finalizeNavigation
(不思考两头reject谬误)。而在finalizeNavigation
中的最初会调用一个markAsReady
办法。
function markAsReady<E = any>(err?: E): E | void { if (!ready) { // still not ready if an error happened ready = !err setupListeners() readyHandlers .list() .forEach(([resolve, reject]) => (err ? reject(err) : resolve())) readyHandlers.reset() } return err}
在markAsReady
中调用了setupListeners
的一个办法。在这个办法中会调用routerHistory.listen()
增加一个函数。
let removeHistoryListener: undefined | null | (() => void)function setupListeners() { // 如果有removeHistoryListener,阐明曾经增加过listener if (removeHistoryListener) return // 调用routerHistory.listen增加监听函数,routerHistory.listen返回一个删除这个listener函数 removeHistoryListener = routerHistory.listen((to, _from, info) => { const toLocation = resolve(to) as RouteLocationNormalized // 确定是否存在重定向 const shouldRedirect = handleRedirectRecord(toLocation) if (shouldRedirect) { pushWithRedirect( assign(shouldRedirect, { replace: true }), toLocation ).catch(noop) return } pendingLocation = toLocation const from = currentRoute.value // 保留from滚动地位 if (isBrowser) { saveScrollPosition( getScrollKey(from.fullPath, info.delta), computeScrollPosition() ) } navigate(toLocation, from) .catch((error: NavigationFailure | NavigationRedirectError) => { // 导航被勾销 if ( isNavigationFailure( error, ErrorTypes.NAVIGATION_ABORTED | ErrorTypes.NAVIGATION_CANCELLED ) ) { return error } // 在钩子中进行了重定向 if ( isNavigationFailure(error, ErrorTypes.NAVIGATION_GUARD_REDIRECT) ) { pushWithRedirect( (error as NavigationRedirectError).to, toLocation ) .then(failure => { // 钩子中的重定向过程中如果导航被勾销或导航冗余,回退一步 if ( isNavigationFailure( failure, ErrorTypes.NAVIGATION_ABORTED | ErrorTypes.NAVIGATION_DUPLICATED ) && !info.delta && info.type === NavigationType.pop ) { routerHistory.go(-1, false) } }) .catch(noop) return Promise.reject() } // 复原历史记录,,但不触发监听 if (info.delta) routerHistory.go(-info.delta, false) // 无奈辨认的谬误,交给全局谬误处理器 return triggerError(error, toLocation, from) }) .then((failure: NavigationFailure | void) => { failure = failure || finalizeNavigation( toLocation as RouteLocationNormalizedLoaded, from, false ) if (failure) { // 如果存在错误信息,回到原始地位,但不触发监听 if (info.delta) { routerHistory.go(-info.delta, false) } else if ( info.type === NavigationType.pop && isNavigationFailure( failure, ErrorTypes.NAVIGATION_ABORTED | ErrorTypes.NAVIGATION_DUPLICATED ) ) { // 谬误类型时导航被勾销或冗余,回退历史记录,但不触发监听 routerHistory.go(-1, false) } } // 触发全局afterEach钩子 triggerAfterEach( toLocation as RouteLocationNormalizedLoaded, from, failure ) }) .catch(noop) })}
能够看到这个监听函数和push
的过程十分相似,与push
不同的是,在触发监听时,一旦呈现了一些错误信息(如导航被勾销、导航时冗余的、地位谬误),须要将历史记录回退到相应地位。
go
的执行流程:
back
back
,回退一个历史记录,相当于go(-1)
。
const router = { // ... back: () => go(-1), // ...}
forward
forward
,后退一个历史记录,相当于go(1)
。
const router = { // ... forward: () => go(1), // ...}
总结
go
、back
、forward
办法最终通过调用history.go
办法,触发popstate
事件(popstate
中的监听函数在第一次路由跳转时被增加),而在popstate
事件中的过程和push
的过程是十分相似的,与push
不同的是,一旦呈现了一些错误信息(如导航被勾销、导航时冗余的、地位谬误),须要将历史记录回退到相应地位。