一、背景和意义
vue-router中vue中的常用组件,其在切换页面时,不会重新请求获取新页面的HTML代码,与常见的页面重定向不一样。本文参照相关源码简要介绍其实现方式。
二、vue-router的使用
在vue中,使用vue-router的方法为:
html
<template>
...
<button @click="toUserInfoPage()" ...>跳转到个人中心</button>
...
</template>
<script>
import { useRouter } from 'vue-router'
const router = useRouter();
...
function toUserInfoPage() {
router.push({ name: "userInfo" });
}
...
</script>
当点"跳转到个人中心"的按钮之后,地址栏上的URL会变成/userInfo
,但浏览器并没有请求/userInfo
页面的html代码,看上去很流畅。
三、vue-router相关源码解读
创建Router对象的代码是在node_modules\vue-router\dist\vue-router.mjs
文件中的createRouter
方法,需要注意的是它是在vue初始化插件的时候调用,而不是在const router = useRouter()
这段代码中调用。createRouter
函数的调用在前,const router = useRouter()
只是获取之前创建Router对象。
createRouter
方法的大致结构如下:
javascript
function createRouter(options) {
...
function push(to) {
return pushWithRedirect(to);
}
function pushWithRedirect(to, redirectedFrom) {
// pushWithRedirect的实现代码比较复杂,这里先省略
}
...
const router = {
...
push,
...
};
return router;
}
接下来在前面的例子中,当点"跳转到个人中心"的按钮之后,调用router.push({ name: "userInfo" })
方法,这里的push
方法也就是createRouter
中定义的push方法。
在调用push
方法时,push
方法会调用pushWithRedirect
方法,该方法的大致结构为:
javascript
function pushWithRedirect(to, redirectedFrom) {
...
return (failure ? Promise.resolve(failure) : navigate(toLocation, from))
.catch((error) => ...)
.then((failure) => {
if (failure) {
...
}
else {
// if we fail we don't finalize the navigation
failure = finalizeNavigation(toLocation, from, true, replace, data);
}
...
return failure;
});
}
其中pushWithRedirect
中的Promise是先写catch再写then,这与很多人的习惯写法不太一样。这里调用了一个finalizeNavigation
方法,该方法的大致结构为:
javascript
function finalizeNavigation(toLocation, from, isPush, replace, data) {
...
// change URL only if the user did a push/replace and if it's not the initial navigation because
// it's just reflecting the url
if (isPush) {
// on the initial navigation, we want to reuse the scroll position from
// history state if it exists
if (replace || isFirstNavigation)
routerHistory.replace(toLocation.fullPath, assign({
scroll: isFirstNavigation && state && state.scroll,
}, data));
else
routerHistory.push(toLocation.fullPath, data);
}
...
}
前面调用router.push({ name: "userInfo" })
方法时,会走routerHistory.push(toLocation.fullPath, data)
这个代码分支。routerHistory
则是useHistoryStateNavigation
这个方法创建的一个对象,该方法的结构为:
javascript
function useHistoryStateNavigation(base) {
const { history, location } = window;
...
function changeLocation(to, state, replace) {
...
try {
// BROWSER QUIRK
// NOTE: Safari throws a SecurityError when calling this function 100 times in 30 seconds
history[replace ? 'replaceState' : 'pushState'](state, '', url);
...
} catch (err) {
...
}
}
function push(to, data) {
...
changeLocation(to, state, false);
...
}
return {
...
push,
...
};
}
也就是push方法又调用了一个changeLocation
方法,第三个参数replace
传false。那么changeLocation
就会调用window.history.pushState修改浏览器的url地址,使用该方法修改url地址时则不会请求相关页面的HTML代码。
另外这里有一行注释:// NOTE: Safari throws a SecurityError when calling this function 100 times in 30 seconds
,也就是在Safari浏览器中,如果频繁调用window.history.pushState可能会报错,vue-router加了一个try...catch
错误处理,我们自己在使用window.history.pushState实现其他功能时,也可以像vue-router那样做一下错误处理。