0%

Vue Router:如何实现一个前端路由

在我们创建 router 对象的时候,会创建一个 history 对象,前面提到 Vue Router 支持三种模式,这里我们重点分析 HTML5 的 history 的模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function createWebHistory(base) {
base = normalizeBase(base)
const historyNavigation = useHistoryStateNavigation(base)
const historyListeners = useHistoryListeners(base, historyNavigation.state, historyNavigation.location, historyNavigation.replace)
function go(delta, triggerListeners = true) {
if (!triggerListeners)
historyListeners.pauseListeners()
history.go(delta)
}
const routerHistory = assign({
// it's overridden right after
location: '',
base,
go,
createHref: createHref.bind(null, base),
}, historyNavigation, historyListeners)
Object.defineProperty(routerHistory, 'location', {
get: () => historyNavigation.location.value,
})
Object.defineProperty(routerHistory, 'state', {
get: () => historyNavigation.state.value,
})
return routerHistory
}

对于 routerHistory 对象而言,它有两个重要的作用,一个是路径的切换,一个是监听路径的变化。

其中,路径切换主要通过 historyNavigation 来完成的,它是 useHistoryStateNavigation 函数的返回值,我们来看它的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
function useHistoryStateNavigation(base) {
const { history, location } = window
let currentLocation = {
value: createCurrentLocation(base, location),
}
let historyState = { value: history.state }
if (!historyState.value) {
changeLocation(currentLocation.value, {
back: null,
current: currentLocation.value,
forward: null,
position: history.length - 1,
replaced: true,
scroll: null,
}, true)
}
function changeLocation(to, state, replace) {
const url = createBaseLocation() +
// preserve any existing query when base has a hash
(base.indexOf('#') > -1 && location.search
? location.pathname + location.search + '#'
: base) +
to
try {
history[replace ? 'replaceState' : 'pushState'](state, '', url)
historyState.value = state
}
catch (err) {
warn('Error with push/replace State', err)
location[replace ? 'replace' : 'assign'](url)
}
}
function replace(to, data) {
const state = assign({}, history.state, buildState(historyState.value.back,
// keep back and forward entries but override current position
to, historyState.value.forward, true), data, { position: historyState.value.position })
changeLocation(to, state, true)
currentLocation.value = to
}
function push(to, data) {
const currentState = assign({},
historyState.value, history.state, {
forward: to,
scroll: computeScrollPosition(),
})
if ( !history.state) {
warn(`history.state seems to have been manually replaced without preserving the necessary values. Make sure to preserve existing history state if you are manually calling history.replaceState:\n\n` +
`history.replaceState(history.state, '', url)\n\n` +
`You can find more information at https://next.router.vuejs.org/guide/migration/#usage-of-history-state.`)
}
changeLocation(currentState.current, currentState, true)
const state = assign({}, buildState(currentLocation.value, to, null), { position: currentState.position + 1 }, data)
changeLocation(to, state, false)
currentLocation.value = to
}
return {
location: currentLocation,
state: historyState,
push,
replace
}
}

该函数返回的 push 和 replace 函数,会添加给 routerHistory 对象上,因此当我们调用 routerHistory.push 或者是 routerHistory.replace 方法的时候实际上就是在执行这两个函数。

push 和 replace 方法内部都是执行了 changeLocation 方法,该函数内部执行了浏览器底层的 history.pushState 或者 history.replaceState 方法,会向当前浏览器会话的历史堆栈中添加一个状态,这样就在不刷新页面的情况下修改了页面的 URL。

我们使用这种方法修改了路径,这个时候假设我们点击浏览器的回退按钮回到上一个 URL,这需要恢复到上一个路径以及更新路由视图,因此我们还需要监听这种 history 变化的行为,做一些相应的处理。