1. Not frompushState
andpopstate
Speaking of
The history mode of routing is implemented based on pushState and PopState.
Please send pushState and 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. The route idea of native JS implementation
- call
pushState
: Modifies only the current onehistory
The contents of the stack will not be navigated to a new page unless refreshed,
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}
Copy the code
- How can that be
pushState
What about updating pages? Repackage the method and also provide a mechanism for updating the page within the method to be constructed.
function push(url) {
try {
// We use try here because we can pass absolute paths to urls with pushState, but we can get an error across domains
history.pushState({}, ' ', url)
} catch (e) {
location.assign(url)
}
updatePage()
}
function updatePage(location) {
// Manipulate dom based on the current location
}
Copy the code
Here we use observer mode to rewrite the update logic:
// Borrow from the history library
const MyRouter = function() {
// Maintain a list of listeners
const listeners = []
// Add a listener
function listen(listener) {
listeners.push(listener)
return function() {
listeners.filter(s= >s ! == listen) } }// Execute all listening events
function call(. args) {
listeners.forEach(listener= >listener(... args)) }return {
listen,
call
}
}
const { listen, call } = myRouter()
Copy the code
So we can rewrite the push method and change updatePage to Call so that we can inject update logic and call it when we push.
So when do you inject listeners?
We use native writing to directly inject the page when it loads:
listen(function(location) {
// Render the corresponding content according to the current path
updatePage(location)
})
Copy the code
The react-router-dom browser-router is the update logic injected by constructor. Browser-router defines the state of the location, and the logic to listen for updates is simple:
listen((location) = > {
this.setState({ location })
})
// When pushing, a call is called to iterate over all listener executions,
// This. SetState will be updated to re-render
Copy the code
- the
popstate
For what?
The popState event can be triggered only when the operation of the address bar has been changed. In other words, the address in the address bar at this time is the previous address. At this time, we can update to the corresponding page according to the current address.
// This function can be wrapped directly into myRouter logic
window.addEventListener("popstate".function (event) {
call(location);
});
Copy the code
- The complete code
<! 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;
};
// Silver mapping of path and routing components
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 logic
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>
Copy the code
The complete code