咳咳,各位靓仔靓女,欢迎来到今天的Vue源码深度游乐园。今天,我们来聊聊Vue Router里那个看似人畜无害,实则暗藏玄机的createWebHistory
。 准备好你们的咖啡和瓜子,咱们发车咯!
一、 History模式:前端路由的浪漫邂逅
在没有前端路由的远古时代(大概也就十年前),我们每次切换页面都要向服务器发起请求,浏览器刷新,用户体验差到爆。后来,前端大佬们发现,我们可以通过一些骚操作,只更新页面的局部内容,而不用刷新整个页面。这就是SPA(Single Page Application)诞生的背景。
而History
模式,就是SPA实现路由的一种方式。它的核心思想是:利用浏览器提供的History API
(pushState
和replaceState
),修改浏览器的URL,但并不触发实际的页面跳转。这样,我们就可以在不刷新页面的情况下,改变URL,然后根据URL来渲染不同的内容。
举个栗子:
<!DOCTYPE html>
<html>
<head>
<title>History 模式演示</title>
</head>
<body>
<h1>欢迎来到首页</h1>
<a href="/about">去关于页面</a>
<script>
const link = document.querySelector('a');
link.addEventListener('click', (event) => {
event.preventDefault(); // 阻止默认的链接跳转
const url = link.getAttribute('href');
history.pushState({ page: 'about' }, 'About', url); // 修改URL
// 根据 URL 加载不同的内容 (这里只是个示例)
if (url === '/about') {
document.body.innerHTML = '<h1>关于我们</h1><a href="/">返回首页</a>';
}
});
</script>
</body>
</html>
在这个简单的例子中,点击“去关于页面”的链接,浏览器URL会变成/about
,但页面不会刷新。我们通过pushState
修改了URL,并手动更新了页面内容。
二、 Vue Router的createWebHistory
:幕后英雄登场
createWebHistory
是Vue Router提供的一个函数,用于创建History
模式的路由实例。它的作用是封装了浏览器的History API
,并提供了一些更高级的功能,比如监听URL变化、处理路由导航等。
让我们来看一下createWebHistory
的源码(简化版,只保留核心逻辑):
// packages/history/src/html5.ts
import { createBaseHistory, NavigationCallback } from './common'
import { createHref } from './utils'
import { HistoryState } from './types'
import { START } from './constants'
export function createWebHistory(base: string = ''): History {
return createBaseHistory({
// 监听popstate事件
listen(callback: NavigationCallback) {
const popStateHandler = () => {
callback({
pathname: location.pathname,
search: location.search,
hash: location.hash,
})
}
window.addEventListener('popstate', popStateHandler)
return () => {
window.removeEventListener('popstate', popStateHandler)
}
},
// 修改URL
push(to: string, data?: HistoryState) {
const url = createHref(base, to);
history.pushState(data, '', url);
},
// 替换URL
replace(to: string, data?: HistoryState) {
const url = createHref(base, to);
history.replaceState(data, '', url);
},
// 获取当前URL
getCurrentLocation() {
return {
pathname: location.pathname,
search: location.search,
hash: location.hash,
}
},
// 获取初始state
ensureLocation() {
// ... 确保初始state存在
}
}, base)
}
可以看到,createWebHistory
返回了一个History
对象,这个对象包含了一系列方法,用于操作浏览器的历史记录:
listen(callback)
: 监听浏览器的popstate
事件。当用户点击浏览器的前进/后退按钮时,会触发popstate
事件。listen
函数会注册一个回调函数,当popstate
事件发生时,这个回调函数会被调用,从而可以更新页面内容。push(to, data)
: 向历史记录中添加一个新的条目,并修改浏览器的URL。to
参数是要跳转的URL,data
参数可以传递一些自定义的数据。replace(to, data)
: 替换当前历史记录中的条目,并修改浏览器的URL。to
参数是要跳转的URL,data
参数可以传递一些自定义的数据。getCurrentLocation()
: 获取当前URL的信息,包括pathname
、search
和hash
。ensureLocation()
: 确保History state存在,解决一些边缘情况下的问题。
三、 createBaseHistory
:基石中的基石
你可能注意到,createWebHistory
内部调用了createBaseHistory
。createBaseHistory
是一个更底层的函数,它封装了操作历史记录的通用逻辑。
createBaseHistory
接收一个options
对象和一个base
参数。options
对象包含以下属性:
listen
: 监听URL变化的回调函数。push
: 修改URL的回调函数。replace
: 替换URL的回调函数。getCurrentLocation
: 获取当前URL的回调函数。
base
参数是应用的根路径。例如,如果你的应用部署在https://example.com/app/
,那么base
就是/app/
。
createBaseHistory
返回一个History
对象,这个对象包含了一些核心的属性和方法:
location
: 当前的URL信息。state
: 当前历史记录的状态。push(to, data)
: 向历史记录中添加一个新的条目,并修改浏览器的URL。replace(to, data)
: 替换当前历史记录中的条目,并修改浏览器的URL。go(delta)
: 在历史记录中前进或后退。back()
: 后退一步。forward()
: 前进一步。listen(callback)
: 监听URL变化的回调函数。destroy()
: 销毁History
对象。
createBaseHistory
的代码比较复杂,涉及到一些状态管理和事件处理的细节。这里我们只给出一个简化版的示例:
// packages/history/src/common.ts
import { Ref, ref, shallowRef } from 'vue'
import { NavigationCallback, HistoryState } from './types'
export function createBaseHistory(options: HistoryOptions, base: string): History {
const { listen, push, replace, getCurrentLocation, ensureLocation } = options
const location: Ref<Location> = ref(getCurrentLocation())
const state: Ref<HistoryState | null> = shallowRef({} as HistoryState);
let listeners: NavigationCallback[] = []
let teardowns: (() => void)[] = []
function triggerListeners(to: Location, from: Location, data?: HistoryState) {
for (const listener of listeners) {
listener(to, from, data)
}
}
function pushWithEvent(to: string, replaceMode: boolean = false, data?: HistoryState) {
const from = location.value;
if(replaceMode){
replace(to, data);
} else {
push(to, data);
}
const newLocation = getCurrentLocation();
location.value = newLocation;
state.value = data || {}; // 更新state
triggerListeners(newLocation, from, data);
}
const history: History = {
location: location.value,
state: state.value,
push: (to: string, data?: HistoryState) => {
pushWithEvent(to, false, data);
},
replace: (to: string, data?: HistoryState) => {
pushWithEvent(to, true, data);
},
go: (delta: number) => history.push(delta.toString()), // 简化处理
back: () => history.go(-1),
forward: () => history.go(1),
listen: (callback: NavigationCallback) => {
listeners.push(callback)
const teardown = () => {
listeners = listeners.filter((cb) => cb !== callback)
}
teardowns.push(teardown)
return teardown
},
destroy: () => {
for (const teardown of teardowns) {
teardown()
}
teardowns = []
listeners = []
},
}
ensureLocation(); // 确保初始location存在
const stopListener = listen((newLocation, from, data) => {
// console.log("location changed", newLocation, from);
location.value = newLocation;
state.value = data || {}; // 更新state
triggerListeners(newLocation, from, data);
});
teardowns.push(stopListener);
return history
}
四、 createHref
:URL的魔法师
createHref
是一个辅助函数,用于生成完整的URL。它接收base
和to
两个参数,并将它们拼接在一起,生成一个完整的URL。
例如,如果base
是/app/
,to
是/about
,那么createHref
会返回/app/about
。
createHref
的代码非常简单:
// packages/history/src/utils.ts
export function createHref(base: string, to: string): string {
return base + to
}
五、 总结:createWebHistory
的炼成之路
现在,我们已经了解了createWebHistory
的实现原理。简单来说,它的作用就是:
- 封装浏览器的
History API
。 - 提供一些更高级的功能,比如监听URL变化、处理路由导航等。
- 通过
createBaseHistory
封装操作历史记录的通用逻辑。 - 使用
createHref
生成完整的URL。
用一个表格来总结一下:
函数/对象 | 作用 |
---|---|
createWebHistory |
创建History 模式的路由实例,是Vue Router提供的API。 |
createBaseHistory |
封装操作历史记录的通用逻辑,是createWebHistory 的底层实现。 |
History |
包含了一系列方法,用于操作浏览器的历史记录,包括push 、replace 、go 、listen 等。 |
createHref |
生成完整的URL,将base 和to 拼接在一起。 |
History API |
浏览器提供的API,包括pushState 、replaceState 和popstate 事件,用于操作浏览器的历史记录。 |
Location |
表示当前URL的信息,包括pathname 、search 和hash 。 |
HistoryState |
存储与历史记录条目关联的自定义数据。可以用来保存路由状态或者其他需要在页面跳转时保留的信息。 |
六、 实战演练:在Vue项目中使用createWebHistory
在Vue项目中,我们可以这样使用createWebHistory
:
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import About from '../views/About.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
}
]
const router = createRouter({
history: createWebHistory(), // 使用createWebHistory
routes
})
export default router
这样,我们就创建了一个使用History
模式的Vue Router实例。
七、 踩坑指南:History
模式的注意事项
在使用History
模式时,需要注意以下几点:
-
服务器配置: 由于
History
模式修改的是浏览器的URL,但并没有触发实际的页面跳转,因此,当用户直接访问某个URL时,服务器需要能够正确地处理这个请求。通常,我们需要将服务器配置为:对于所有未匹配到的请求,都返回index.html
。这样,Vue Router就可以接管路由,并渲染正确的页面。 -
base
参数: 如果你的应用部署在子目录中,需要设置base
参数。例如,如果你的应用部署在https://example.com/app/
,那么base
应该设置为/app/
。 -
URL编码: 在URL中传递参数时,需要注意URL编码。例如,空格应该编码为
%20
,特殊字符应该编码为相应的URL编码。
八、 进阶修炼:自定义History
模式
如果你对Vue Router的默认History
模式不满意,还可以自定义History
模式。例如,你可以实现一个基于sessionStorage
的History
模式,或者一个基于hash
的History
模式。
自定义History
模式需要实现History
接口,并提供相应的listen
、push
、replace
等方法。
九、总结的总结
今天我们深入浅出地探讨了Vue Router中createWebHistory
的实现原理。从History API
到createBaseHistory
,再到createHref
,我们一步步揭开了History
模式的神秘面纱。希望这次旅程能让你对Vue Router有更深刻的理解。
记住,源码就像一本武功秘籍,只有不断研读,才能练成绝世神功!
好了,今天的讲座就到这里,下次再见! 记得点赞收藏哦!