手写hash和history路由
hash路由实现
一、hash路由原理
- hash是url中可读可写的字符串,改变hash不会导致页面重新加载
- 通过监听hashchange事件,可以获得hash改变之后的回调
- 根据不同的hash值,将对应内容插到元素上即可
二、创建router类
class newHashRouter {
constructor(routes = []) {
this.routes = routes; // 存储路由集合
this.currentHash = ""; // 存储当前hash值
this.matchRouter = this.matchRouter.bind(this); // 创建设置路由方法
// 页面加载完之后,需要匹配一次路由内容(相当于初始化)
window.addEventListener("load", this.matchRouter, false);
// 页面触发hashchange的时候,匹配路由
window.addEventListener("hashchange", this.matchRouter, false);
}
// 路由匹配方法,匹配路由,将路由的内容设置到页面上
matchRouter(event) {
let hash = "";
if (event.newURL) {
// hashchange才有这个newURL参数,针对hashchange事件获取hash值
hash = this.getUrlPath(event.newURL);
} else {
// 针对load事件获取hash值
hash = this.getUrlPath(location.hash);
}
// 设置路由
this.setRouter(hash);
}
// 设置路由
setRouter(hash) {
this.currentHash = hash;
// 查找当前匹配到的路由
let curRoute = this.routes.find(
(route) => route.path === this.currentHash
);
// 如果找不到,默认返回首页路由
if (!curRoute) {
curRoute = this.routes.find((route) => route.path === "/index");
}
let { component } = curRoute; // 解构出路由的component字段
// 将结构出来的component挂载在页面
document.getElementById("main").innerHTML = component;
}
// 获取url上的hash值
getUrlPath(url) {
// 获取hash
return url.indexOf("#") >= 0 ? url.slice(url.indexOf("#") + 1) : "/";
}
}
三、实例化路由对象
const router = new newHashRouter([
{
path: "/",
name: "home",
component: "<div>首页内容</div>"
},
{
path: "/shop",
name: "shop",
component: "<div>商城内容</div>"
},
{
path: "/shopping-cart",
name: "shopping-cart",
component: "<div>购物车内容</div>"
},
{
path: "/mine",
name: "mine",
component: "<div>我的内容</div>"
}
]);
四、路由实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"
name="viewport"
/>
<title>实现简单的hash路由</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
height: 100%;
}
#main {
height: calc(100vh - 50px);
display: flex;
align-items: center;
justify-content: center;
font-size: 3em;
}
#nav {
height: 50px;
position: fixed;
left: 0;
bottom: 0;
width: 100%;
display: flex;
}
#nav a {
width: 25%;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid black;
}
#nav a:not(:last-of-type) {
border-right: none;
}
</style>
</head>
<body>
<main id="main"></main>
<nav id="nav">
<a href="#/">首页</a>
<a href="#/shop">商城</a>
<a href="#/shopping-cart">购物车</a>
<a href="#/mine">我的</a>
</nav>
</body>
<script>
class xsHashRouter {
constructor(routes = []) {
this.routes = routes; // 存储路由集合
this.currentHash = ""; // 存储当前hash值
this.matchRouter = this.matchRouter.bind(this); // 创建设置路由方法
// 页面加载完之后,需要匹配一次路由内容(相当于初始化)
window.addEventListener("load", this.matchRouter, false);
// 页面触发hashchange的时候,匹配路由
window.addEventListener("hashchange", this.matchRouter, false);
}
// 路由匹配方法,匹配路由,将路由的内容设置到页面上
matchRouter(event) {
let hash = "";
if (event.newURL) {
// hashchange才有这个newURL参数,针对hashchange事件获取hash值
hash = this.getUrlPath(event.newURL);
} else {
// 针对load事件获取hash值
hash = this.getUrlPath(location.hash);
}
this.setRouter(hash);
}
// 设置路由
setRouter(hash) {
this.currentHash = hash;
// 查找当前匹配到的路由
let curRoute = this.routes.find(
(route) => route.path === this.currentHash
);
// 如果找不到,默认返回首页路由
if (!curRoute) {
curRoute = this.routes.find((route) => route.path === "/index");
}
let { component } = curRoute; // 解构出路由的component字段
// 将结构出来的component挂载在页面
document.getElementById("main").innerHTML = component;
}
// 获取url上的hash值
getUrlPath(url) {
// 获取hash
return url.indexOf("#") >= 0 ? url.slice(url.indexOf("#") + 1) : "/";
}
}
const router = new xsHashRouter([
{
path: "/",
name: "home",
component: "<div>首页内容</div>"
},
{
path: "/shop",
name: "shop",
component: "<div>商城内容</div>"
},
{
path: "/shopping-cart",
name: "shopping-cart",
component: "<div>购物车内容</div>"
},
{
path: "/mine",
name: "mine",
component: "<div>我的内容</div>"
}
]);
</script>
</html>
五、实际效果
history路由实现
一、history原理
history方法:
- window.history.go 可以跳转到浏览器会话历史中的指定的某一个记录页
- window.history.forward 指向浏览器会话历史中的下一页,跟浏览器的前进按钮相同
- window.history.back 返回浏览器会话历史中的上一页,跟浏览器的回退按钮功能相同
- window.history.pushState 可以将给定的数据压入到浏览器会话历史栈中
- window.history.replaceState 将当前的会话页面的url替换成指定的数据
popstate方法:
- history.pushState和history.replaceState方法是不会触发popstate事件的
- 但是浏览器的某些行为会导致popstate,比如go、back、forward
- popstate事件对象中的state属性,可以理解是我们在通过history.pushState或history.replaceState方法时,传入的指定的数据
核心:
pushState和replaceState都会改变url且不会刷新页面
pushState往history中插入一条,replaceState是替换当前的这条会话历史
二、创建router类
class newHistoryRouter {
constructor(path = []) {
// 创建路由对象,用key,value存储路由
this.routes = path;
// 监听popstate方法(),触发时更新页面
this.bindPopState();
}
// 初始化挂载路由
init(path) {
history.replaceState({ path: path }, null, path);
this.setRouter(path);
}
// 路由跳转
go(path) {
// history插入新路由
history.pushState({ path: path }, null, path);
this.setRouter(path);
}
// 监听popstate方法,相当于监听go,forward,back这些方法
// 因为触发go,forward,back这些方法,之后会触发监听popstate
bindPopState() {
window.addEventListener("popstate", (e) => {
const path = e.state && e.state.path;
this.setRouter(path);
});
}
// 设置路由
setRouter(path) {
let curRoute = this.routes.find((route) => route.path === path);
if (!curRoute) {
this.routes.find((route) => route.path === "/");
}
let { component } = curRoute; // 解构出路由的component字段
// 将结构出来的component挂载在页面
document.getElementById("main").innerHTML = component;
}
}
三、实例化路由对象
const router = new newHistoryRouter([
{
path: "/",
name: "home",
component: "<div>首页内容</div>"
},
{
path: "/shop",
name: "shop",
component: "<div>商城内容</div>"
},
{
path: "/shopping-cart",
name: "shopping-cart",
component: "<div>购物车内容</div>"
},
{
path: "/mine",
name: "mine",
component: "<div>我的内容</div>"
}
]);
// 初始化入口链接
router.init(location.pathname);
四、路由实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"
name="viewport"
/>
<title>实现简单的history路由</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
height: 100%;
}
#main {
height: calc(100vh - 50px);
display: flex;
align-items: center;
justify-content: center;
font-size: 3em;
}
#nav {
height: 50px;
position: fixed;
left: 0;
bottom: 0;
width: 100%;
display: flex;
}
#nav a {
width: 25%;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid black;
}
#nav a:not(:last-of-type) {
border-right: none;
}
</style>
</head>
<body>
<main id="main"></main>
<nav id="nav">
<a onclick="clickhref('/')">首页</a>
<a onclick="clickhref('/shop')">商城</a>
<a onclick="clickhref('/shopping-cart')">购物车</a>
<a onclick="clickhref('/mine')">我的</a>
</nav>
</body>
<script>
class newHistoryRouter {
constructor(path = []) {
// 创建路由对象,用key,value存储路由
this.routes = path;
// 监听popstate方法(),触发时更新页面
this._bindPopState();
}
// 初始化挂载路由
init(path) {
history.replaceState({ path: path }, null, path);
this.setRouter(path);
}
// 路由跳转
go(path) {
// history插入新路由
console.log(path);
history.pushState({ path: path }, null, path);
this.setRouter(path);
}
// 监听popstate方法
_bindPopState() {
window.addEventListener("popstate", (e) => {
const path = e.state && e.state.path;
this.setRouter(path);
});
}
// 设置路由
setRouter(path) {
let curRoute = this.routes.find((route) => route.path === path);
if (!curRoute) {
this.routes.find((route) => route.path === "/");
}
let { component } = curRoute; // 解构出路由的component字段
// 将结构出来的component挂载在页面
document.getElementById("main").innerHTML = component;
}
}
const router = new newHistoryRouter([
{
path: "/",
name: "home",
component: "<div>首页内容</div>"
},
{
path: "/shop",
name: "shop",
component: "<div>商城内容</div>"
},
{
path: "/shopping-cart",
name: "shopping-cart",
component: "<div>购物车内容</div>"
},
{
path: "/mine",
name: "mine",
component: "<div>我的内容</div>"
}
]);
router.init(location.pathname);
function clickhref(path) {
router.go(path);
}
</script>
</html>
五、实际效果
六、注意
history模式刷新可以会报错,因为浏览器会把这个链接当成一个正式的请求发送到服务器,如果找不到这个静态资源就会报错。
所以一般情况下需要配置nginx
// 告诉服务器,当我们访问的路径资源不存在的时候,默认指向静态资源index.html
location / { try_files $uri $uri/ /index.html; }
总结
- hash模式不用服务器配合,通过监听hashchange事件来处理前端业务逻辑,当拿到回调之后,匹配路由如果有就将对应的内容插入指定节点
- history模式需要服务器配合,通过监听pushState及replaceState事件,这两个操作时不会触发页面刷新结合popstate方法,对go、back、forward操作时,通过pushState及replaceState事件进行页面链接的替换及将对应的内容插入指定节点
转载自:https://juejin.cn/post/7207406497507835961