在各种框架中,路由的实现主要有两种方式:
- 用 hashchange 事件监听 url 来实现;
- 用 history.pushState() + popstate 来实现。
先看第一种,用 hashchange 实现路由相当简单:
// html
<a href="#/">首页</a>
<a href="#/blog">博客</a>
<a href="#/college">学院</a>
当点击 a 标签,hash 值发生变化,触发事件,回调函数可得到新 url 的 hash 值:
// js
window.addEventListener('hashchange', () => {
console.log(location.hash.slice(1)) // 得到 '/'、'/blog' 或 '/college'
})
配置好之后如下:
window.addEventListener('hashchange', () => {
const currHash = location.hash.slice(1),
view = document.querySelector('#view');
if (currHash == '/blog') {
view.innerHTML = '博客';
} else if (currHash == '/college') {
view.innerHTML = '学院';
} else {
view.innerHTML = '首页';
}
})
简单封装如下:
// 路由配置
const routes = [
{ path: '/', page: '首页' },
{ path: '/blog', page: '博客' },
{ path: '/college', page: '学院' },
]
// 路由方法
function route() {
const currHash = location.hash.slice(1),
view = document.querySelector('#view');
let page = '';
for (let i = 0; i < routes.length; i++) {
if (routes[i].path == currHash) {
page = routes[i].page;
break;
}
}
view.innerHTML = page;
}
// 路由注册
window.addEventListener('hashchange', () => {
route()
})
window.addEventListener('load', () => {
route()
})
如果仿 vue-router 的用法,封装后大概这样:
// html
<router>
<a href="#/">首页</a>
<a href="#/blog">博客</a>
<a href="#/college">学院</a>
</router>
<router-view></router-view>
// js
// 路由构造函数
function Router(routes) {
if (this instanceof Router) {
this.routes = routes.routes;
this.init();
}
else {
return new Router(routes);
}
}
// 路由刷新
Router.prototype.refresh = function () {
const currHash = location.hash.slice(1);
for (let i = 0; i < this.routes.length; i++) {
if (this.routes[i].path == currHash) {
document.querySelector('router-view').innerHTML = this.routes[i].component;
break;
}
}
}
// 路由初始化
Router.prototype.init = function () {
window.addEventListener('hashchange', this.refresh.bind(this))
window.addEventListener('load', this.refresh.bind(this))
}
// 路由配置
const routes = [
{ path: '/', component: '首页' },
{ path: '/blog', component: '博客' },
{ path: '/college', component: '学院' },
]
// 路由使用
new Router({
routes
})
当然,这只是模仿 vue 路由的部分功能和使用,并不是 vue 路由源码,再看看另一种模式 history.pushState() + popstate :
// html
<router>
<a href="/">首页</a>
<a href="/blog">博客</a>
<a href="/college">学院</a>
</router>
<router-view></router-view>
// js部分
// 路由配置
const routes = [
{ path: '/', component: '首页' },
{ path: '/blog', component: '博客' },
{ path: '/college', component: '学院' }
]
// 路由刷新
function refresh(path) {
for (let i = 0; i < routes.length; i++) {
if (routes[i].path == path) {
document.querySelector('router-view').innerHTML = routes[i].component;
break;
}
}
}
// 监听onpopstate事件
window.addEventListener('onpopstate', e => {
const state = e.state.route;
refresh(state);
})
// 监听点击事件
document.querySelector('router').addEventListener('click', function (e) {
if (e.target.localName == 'a') {
const path = e.target.getAttribute('href');
refresh(path);
history.pushState({ route: path }, null, path);
e.preventDefault();
}
})
封装之后:
// 构造函数
function Router(routes) {
if (this instanceof Router) {
this.routes = routes.routes;
this.init();
} else {
return new Router()
}
}
// 路由刷新
Router.prototype.refresh = function () {
const view = document.querySelector('router-view');
for (let i = 0; i < this.routes.length; i++) {
if (this.routes[i].path == this.path) {
view.innerHTML = this.routes[i].component;
break;
}
}
}
// 路由初始化
Router.prototype.init = function () {
window.onpopstate = e => {
this.path = e.state.route;
this.refresh();
}
document.querySelector('router').addEventListener('click', e => {
if (e.target.localName == 'a') {
e.preventDefault();
this.path = e.target.getAttribute('href');
this.refresh();
history.pushState({ route: this.path }, null, this.path);
}
})
}
// 路由配置
const routes = [
{ path: '/', component: '首页' },
{ path: '/blog', component: '博客' },
{ path: '/college', component: '学院' }
]
// 路由使用
new Router({
routes
})
history 的详细解读另写一篇 history详解
一句话日记:有事干的时候才觉得时间太珍贵