1. 不从pushState和popstate说起
路由的history模式是基于pushState和popstate实现的。
Note that just calling history.pushState() or history.replaceState() won’t trigger a popstate event. The popstate event will be triggered by doing a browser action such as a click on the back or forward button (or calling history.back() or history.forward() in JavaScript).
2. 原生js实现路由思路说明
- 调用
pushState:只会修改当前history栈的内容,不刷新的话并不会导航到新页面,
location.href // "https://juejin.cn/editor/drafts/69724xxxxxx02504968"
history.state // null
history.pushState({id: 1}, 'id1', '/a')
location.href // "https://juejin.cn/a"
history.state // {id: 1}
复制代码
- 那怎么可以让
pushState更新页面呢?重新封装一下这个方法,并同时在要构造的方法内部提供更新页面的机制。
function push(url) {
try {
// 这里用try是因为pushState可以的url可以传入绝对路径,但在跨域的时候会报错
history.pushState({}, '', url)
} catch (e) {
location.assign(url)
}
updatePage()
}
function updatePage(location) {
// 根据当前location去操作dom
}
复制代码
这里用观察者模式,改写下更新逻辑:
// 借鉴于history库
const MyRouter = function() {
// 维护一组监听列表
const listeners = []
// 添加监听函数
function listen(listener) {
listeners.push(listener)
return function() {
listeners.filter(s => s !== listen)
}
}
// 执行所有监听事件
function call(...args) {
listeners.forEach(listener => listener(...args))
}
return {
listen,
call
}
}
const { listen, call } = myRouter()
复制代码
这样我们可以改写一下push方法,将updatePage改成call,这样我们可以注入更新逻辑然后在push时调用。
那什么时候注入监听器呢?
我们用原生写的话直接在页面加载的时候去注入就可以了:
listen(function(location) {
// 根据当前路径 渲染对应的内容
updatePage(location)
})
复制代码
react-router-dom的browser-router是在constructor的时候注入的更新逻辑。首先browser-router定义了location的state, 然后监听更新逻辑很简单:
listen((location) => {
this.setState({ location })
})
// 这样 push的时候,会调用call去遍历所有listener执行,
// 就会触发this.setState去更新,从而重新渲染 <BrowserRouter />
复制代码
- 那
popstate用来做什么的?
可以触发popstate事件的都是已经改变了地址栏的操作,换句话说,此时的地址栏内的地址是上一个地址了,这个时候我们可以根据当前地址去更新至对应的页面了。
// 这个功能是可以直接封装到myRouter的逻辑内的
window.addEventListener("popstate", function (event) {
call(location);
});
复制代码
- 完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
a {
margin-right: 20px;
}
</style>
</head>
<div class="link-wrapper">
<a href="javascript:;" data-href="/home">home</a>
<a href="javascript:;" data-href="/abc">abc</a>
<a href="javascript:;" data-href="/def">def</a>
<a href="javascript:;" data-href="/ghi">ghi</a>
</div>
<div class="content"></div>
<body>
<script>
const history = window.history;
const createSomeElement = (textContent) => {
const div = document.createElement("div");
div.textContent = textContent;
return div;
};
// 路径与路由组件的银映射
const pages = {
"/home": createSomeElement("home"),
"/abc": createSomeElement("abc"),
"/def": createSomeElement("def"),
"/ghi": createSomeElement("ghi")
};
const IfRedirectPath = () => {
const pagesId = Object.keys(pages);
if (path == "/" || !pagesId.includes(path)) {
path = "/home";
history.pushState({}, "", "/home");
}
return path;
}
const renderPage = (path) => {
// redirect的逻辑
path = IfRedirectPath(path);
const cont = document.querySelector(".content");
const firstChild = cont.firstChild;
if (firstChild) {
cont.removeChild(firstChild);
}
cont.appendChild(pages[path]);
};
const myRouter = function () {
const handlers = [];
const listen = (listener) => {
handlers.push(listener);
return function unlisten() {
handlers.filter((h) => h !== listener);
};
};
const call = (location) => {
handlers.forEach((cb) => cb(location));
};
window.addEventListener("popstate", function () {
call({ url: window.location.pathname });
});
return {
listen,
call
};
};
const { listen, call } = myRouter();
const push = (url, state = {}, title) => {
try {
history.pushState(state, title, url);
} catch (e) {
window.assign(url);
}
call({ state, title, url });
};
window.addEventListener("beforeunload", function () {
unListen && unListen();
});
document
.querySelector(".link-wrapper")
.addEventListener("click", function (e) {
if (e.target.tagName !== "A") return;
push(e.target.dataset.href);
});
var unListen = listen(function (loc) {
renderPage(loc.url);
});
window.addEventListener("load", function () {
const path = window.location.pathname;
push(path);
});
</script>
</body>
</html>
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END






















![[桜井宁宁]COS和泉纱雾超可爱写真福利集-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/4d3cf227a85d7e79f5d6b4efb6bde3e8.jpg)

![[桜井宁宁] 爆乳奶牛少女cos写真-一一网](https://www.proyy.com/skycj/data/images/2020-12-13/d40483e126fcf567894e89c65eaca655.jpg)