对于前端路由来说,路由的映射函数通常是进行一些DOM的显示和隐藏。当访问不同路径时,会显示不同的页面组件
前端路由的两种原生js实现方式
Hash模式
- 基于location.hash实现
- www.example.com#home 这个网站的 location.hash 为 '#home'
- URL中hash值只是客户端的一种状态,当向服务器发请求时,hash部分不会被发送
- hash值的改变会在浏览器的访问历史中增加一个记录,因此能通过浏览器的回退、前进按钮控制hash的切换
- hashchange 事件 可以监听hash变化
- 的 href 和 location.hash 可以触发hashchange
代码实现
<script type="module">
export class BaseRouter {
// 创建一个路由表
constructor(list) {
this.list = list;
}
// 页面渲染函数
render(state) { // state表示当前的路由状态(例如一个路径)
let e = this.list.find(e => e.path === state);
e = e ? e : this.list.find(e => e.path === '*'); // 默认路由项 *
ELEMENT.innerText = e.component;
}
}
export class HashRouter extends BaseRouter {
constructor(list) {
super(list);
this.handler(); // 初始化路由
// 监听 hashchange 事件
window.addEventListener('hashchange', e => {
this.handler();
});
}
// hash改变时,重新渲染页面
handler() {
this.render(this.getState()); // 当前URL的hash变化时,调用this.handler()响应这个变化
}
// 获取hash值
getState() {
const hash = window.location.hash;
return hash ? hash.slice(1) : '/'; // 如果存在hash,去掉第一个字符"#"返回,否则返回 '/'
}
// push新的页面
push(path) {
window.location.hash = path; // 更改window.location.hash会导致URL的hash改变,但不会重新加载页面,这是SPA路由跳转的常用技术
}
// 获取默认页URL
getURL(path) {
const href = window.location.href;
const i = href.indexOf('#'); // 找到URL中#字符的位置
const base = i >= 0 ? href.slice(0, i) : href; // #存在则提取从开始到#之前的所有部分作为baseURL,否则提取整个href(效果等价)
return base + '#' + path;
}
// 替换页面
replace(path) {
window.location.replace(this.getURL(path)); // replace()方法会将当前URL替换为新的URL,会替换浏览器历史记录中的当前条目,而不是添加一个新条目
}
// 前进/后退浏览历史
go(n) {
window.history.go(n);
}
}
const router = new HashRouter([
{ path: '/', component: 'Home' },
{ path: '/about', component: 'About' },
{ path: '/contact', component: 'Contact' },
{ path: '/help', component: 'Help' },
{ path: '*', component: '404 Not Found' }
]);
// 将router对象暴露给window对象,使路由器可在全局作用域访问
window.router = router;
</script>
<body>
<div id="ELEMENT">Home</div>
<button onclick="router.push('/about')">Go to About</button>
<button onclick="router.push('/contact')">Go to Contact</button>
<button onclick="router.replace('/help')">Replace with Help</button>
<button onclick="router.go(-1)">Go Back</button>
<button onclick="router.go(1)">Go Forward</button>
</body>
实现了一个简单的 Hash 模式的路由
History模式
- html5提供了 History API,可以直接通过history.pushState()(新增历史记录) 和 history.replaceState()(替换当前的历史记录) 在不刷新页面的情况下改变浏览器的历史记录
- History API路由系统实现的路由方法 比基于哈希的路由更现代和优雅,因为它允许更干净的 URLs(不包含哈希符号 #)
- popstate 事件 监听url变化
- 另外,history.pushState()和history.replaceState()不会触发popState事件,需要手动触发页面渲染
pushState() 和 replaceState()
pushState()和replaceState()的三个参数:
- state object(
state) 状态对象
- 类型:object
- 用途:在popstate事件触发时从event.state中获得,可以用来存储滚动位置、页面标题或其他与历史条目相关的数据,为了性能考虑一般只存储轻量级数据
- title(
title)
- 类型:string
- 用途:理论上为了给新历史条目设置标题,但目前大多数浏览器没有实现这个功能,所以一般置为null
- url(
url)
- 类型:string
- 用途:设置新历史条目的 URL。这个 URL 应该与当前域名下的 URL 相同,或者是相对于当前 URL 的路径。它会改变浏览器地址栏中显示的 URL,但不会导致页面重新加载
- 注意:新的 URL 不应跨域,且不能违反同源策略。此外,即使 URL 改变了,页面不会重新加载,因此你需要确保你的应用能够根据 URL 的变化来正确渲染内容
// 添加一个新的历史记录
history.pushState({ page: 1 }, "", "page1.html");
// 替换当前的历史记录
history.replaceState({ page: 2 }, "", "page2.html");
代码实现
<script type="module">
export class BaseRouter {
constructor(list) {
this.list = list;
}
render(state) {
let e = this.list.find(e => e.path === state);
e = e ? e : this.list.find(e => e.path === '*');
ELEMENT.innerText = e.component;
}
}
export class HistoryRouter extends BaseRouter {
constructor(list) {
super(list);
this.handler();
// 监听 popstate事件
window.addEventListener('popstate', e => {
this.handler();
});
}
// 渲染事件
handler() {
this.render(this.getState());
}
getState() {
const path = window.location.pathname;
return path ? path : '/';
}
push(path) {
history.pushState(null, null, path); // 向历史记录添加一个新条目
this.handler(); // 更新视图
}
replace(path) {
history.replaceState(null, null, path); // 替换当前的历史记录条目
this.handler();
}
go(n) {
window.history.go(n);
}
}
const router = new HistoryRouter([
{ path: '/', component: 'Home' },
{ path: '/about', component: 'About' },
{ path: '/contact', component: 'Contact' },
{ path: '/help', component: 'Help' },
{ path: '*', component: '404 Not Found' }
]);
window.router = router;
</script>
<body>
<div id="ELEMENT">Home</div>
<button onclick="router.push('/about')">Go to About</button>
<button onclick="router.push('/contact')">Go to Contact</button>
<button onclick="router.replace('/help')">Replace with Help</button>
<button onclick="router.go(-1)">Go Back</button>
<button onclick="router.go(1)">Go Forward</button>
</body>
实现了一个简单的 History 模式的路由
我是栖夜,感谢阅读。