Vue-Router 中 hash 和 history 的区别
hash 和 history 的区别
- hash 模式: 通过 URL 中的哈希 (#) 部分来保持状态。
- history 模式: 通过 HTML5 History API 来保持状态。
Hash 模式工作机制
- URL 形式:
http://example.com/#/path
- 实现方式: 使用 URL 中的哈希 (#) 部分,后面的路径由前端路由控制。
- 浏览器兼容性: 兼容性好,支持所有浏览器,包括较旧的版本。
- 服务器配置: 不需要特殊配置,因为请求不会被发送到服务器。
History 模式工作机制
- URL 形式:
http://example.com/path
- 实现方式: 使用 HTML5 History API (
pushState
和replaceState
) 来管理前端路由。 - 浏览器兼容性: 现代浏览器支持,但不兼容 IE9 及更早版本。
- 服务器配置: 会发送真实的请求,所以需要服务器配置以支持单页面应用。服务器需要返回
index.html
对于所有的路由请求,否则刷新页面时会出现 404 错误。
使用 nodejs 作为服务,可以这样写:
fs.readFile('index.html', 'utf-8', (err, content) => {
if (err) {
console.log('We cannot open "index.htm" file.')
}
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8'
})
res.end(content)
})
Vue Router 源码分析
Hash 模式源码解析
初始化:
Vue Router 初始化时,根据配置选择hash
模式并创建相应 实例(HashHistory
),类在初始化时确保 URL 包含哈希值。启动监听:
在HashHistory
类中,会启动对hashchange
事件的监听,这样每当 URL 哈希部分变化时,路由器能够感知到变化并做出相应处理。获取和设置哈希:
通过getHash
和pushHash
方法,获取当前哈希值和向 URL 添加新的哈希值。这些方法操作 window.location.hash 来读取和更新哈希。
以下是相关源码的简化版分析:
HashHistory 类:
class HashHistory extends History {
constructor(router, base) {
super(router, base);
// 确保 URL 有哈希值
ensureSlash();
}
setupListeners() {
window.addEventListener('hashchange', () => {
// 当 hash 变化时,触发响应逻辑
this.transitionTo(getHash(), () => {
// 更新 URL 或其他操作
replaceHash(this.current.fullPath);
});
});
}
push(location) {
// 导航到新地址
this.transitionTo(location, route => {
pushHash(route.fullPath);
});
}
replace(location) {
// 替换当前地址
this.transitionTo(location, route => {
replaceHash(route.fullPath);
});
}
}
辅助函数:
// 获取当前哈希值
function getHash() {
let href = window.location.href;
let index = href.indexOf('#');
return index === -1 ? '' : href.slice(index + 1);
}
// 更新哈希值
function pushHash(path) {
window.location.hash = path;
}
// 替换哈希值
function replaceHash(path) {
let i = window.location.href.indexOf('#');
window.location.replace(
window.location.href.slice(0, i >= 0 ? i : 0) + '#' + path
);
}
// 确保 URL 有哈希值
function ensureSlash() {
if (window.location.hash.charAt(1) !== '/') {
window.location.hash = '/';
}
}
History 模式源码解析
history 模式的实现主要依赖于 HTML5 History API,包括 pushState
和 replaceState
方法,以及监听 popstate
事件。以下是一个简要的分析:
初始化:
Vue Router 初始化时,根据配置选择history
模式并创建相应的History
实例(HTML5History
)。启动监听:
在HTML5History
类中,会启动对popstate
事件的监听,这样每当浏览器的历史记录发生变化时,路由器能够感知到变化并做出相应处理。导航操作:
通过pushState
和replaceState
方法更新浏览器的历史记录,同时不会触发页面刷新。
HTML5History 类:
class HTML5History extends History {
constructor(router, base) {
super(router, base);
// 确保基路径存在
this.ensureSlash();
}
setupListeners() {
window.addEventListener('popstate', event => {
this.transitionTo(getLocation(this.base), route => {
// 更新 URL 或其他操作
});
});
}
push(location, onComplete, onAbort) {
const { fullPath } = this.router.resolve(location, this.current, false);
this.transitionTo(fullPath, route => {
pushState(cleanPath(this.base + fullPath));
onComplete && onComplete(route);
}, onAbort);
}
replace(location, onComplete, onAbort) {
const { fullPath } = this.router.resolve(location, this.current, false);
this.transitionTo(fullPath, route => {
replaceState(cleanPath(this.base + fullPath));
onComplete && onComplete(route);
}, onAbort);
}
}
辅助函数:
function getLocation(base) {
let path = window.location.pathname;
if (base && path.indexOf(base) === 0) {
path = path.slice(base.length);
}
return (path || '/') + window.location.search + window.location.hash;
}
function pushState(url) {
window.history.pushState({ key: genKey() }, '', url);
}
function replaceState(url) {
window.history.replaceState({ key: genKey() }, '', url);
}
function cleanPath(path) {
return path.replace(/\/\//g, '/');
}
function genKey() {
return Date.now().toFixed(3);
}
路由匹配原理
transitionTo
方法是路由器的核心方法,用于进行路由匹配和导航。Vue Router 在切换页面时利用了 Vue 的虚拟 DOM 机制来高效地管理组件的创建和销毁。
transitionTo 源码解析:
// Vue Router 源码简化版
transitionTo(location, onComplete, onAbort) {
const route = this.match(location);
this.confirmTransition(route, () => {
this.updateRoute(route);
onComplete && onComplete(route);
}, onAbort);
}
<router-view>
组件的核心是一个渲染函数,它根据当前的路由状态动态渲染对应的组件。
// Vue Router 源码简化版
export default {
name: 'RouterView',
functional: true,
render(h, { parent, data }) {
const route = parent.$route;
const matched = route.matched[0];
if (!matched) {
return h();// 如果没有匹配的路由,则返回空
}
// 获取默认的路由组件(假设没有使用命名视图,默认情况下路由组件存储在 default 属性中)
const component = matched.components.default;
// 使用 Vue 的渲染函数 h 创建一个虚拟节点,component 是要渲染的 Vue 组件。
return h(component, data);
}
}
手写 mini hash router
这个任务要求实现一个简单的 hash 路由器,该路由器应该具备路由匹配、导航等功能。
实现路由匹配
首先,需要实现路由匹配的功能。这个功能应该能够根据当前的 URL 路径,找到对应的组件或页面。
实现导航
接下来,需要实现导航功能。这个功能应该能够根据用户点击的链接,更新 URL 路径,并触发路由匹配和页面更新。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<script>
class Router {
constructor() {
this.routes = {} // 存放路由path及callback
this.currentUrl = ''
// 绑定 this 到 refresh 方法
this.refresh = this.refresh.bind(this)
// 监听路由change调用相对应的路由回调
window.addEventListener('load', this.refresh, false)
window.addEventListener('hashchange', this.refresh, false)
}
// 注册路由及其对应的回调
route(path, callback) {
console.log(callback);
this.routes[path] = callback || function () {}
}
// 更新 currentUrl 并调用相应的回调
refresh() {
this.currentUrl = location.hash.slice(1) || '/'
this.routes[this.currentUrl] && this.routes[this.currentUrl]()
}
// 手动跳转到指定路径
push(path) {
location.hash = path
}
}
// 使用示例
const router = new Router()
router.route('/', function () {
console.log('Home page')
})
router.route('/about', function() {
console.log('About page');
});
// 手动导航
router.push('/about'); // 会在控制台输出 'About page'
</script>
</body>
</html>
总结
- hash 模式: 兼容性好,支持所有浏览器,包括较旧的版本。
- history 模式: 需要 HTML5 History API 支持,但提供了更好的用户体验。
- 服务器配置: hash 模式不需要特殊配置,而 history 模式需要服务器配置以支持单页面应用。