Understanding SPA
Modern front-end projects are mostly single page Web applications (SPA), in which the path is an important part.
SPA is the abbreviation of single page web application, which is translated into single page web application.
In short, SPA is a WEB project with only one HTML page. Once the page is loaded, SPA will not reload or jump the page because of the user's operation Instead, JS is used to dynamically transform the HTML content, so as to simulate the jump between multiple views.
Forward routing
In short, it is to match a special url for each view presentation form in SPA while ensuring that there is only one HTML page, and no page refresh and jump when interacting with users. This special url is used to refresh, forward, backward and SEO.
We need to realize the following two points:
- Change the url and don't let the browser send requests like the server.
- You can listen for url changes
- You can dynamically change the URL in the browser address bar without refreshing the page
hash mode and history mode are used to implement the above functions
Hash mode
After the url, add ා, such as http://127.0.0.1:5500/front-end routing / hash.html ා / page1. After the url, the ා / page1 is the hash value
- Changes in hash values do not cause browsers to send requests like servers
- location.hash can get the hash value
- hashchange is a function of a call that changes the hash value
Based on the above three points, we can write a routing instance
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Document</title> </head> <body> <ul> <li><a href="#/">/</a></li> <li><a href="#/page1">page1</a></li> <li><a href="#/page2">page2</a></li> </ul> <div class="content-div"></div> </body> <script> class RouterClass { constructor() { this.routes = {}; // Record cb corresponding to path identifier this.currentUrl = ""; // Record hash for convenience of cb window.addEventListener("load", () => this.render()); window.addEventListener("hashchange", () => this.render()); } /* Initialization */ static init() { window.Router = new RouterClass(); } /* Register routes and callbacks */ route(path, cb) { this.routes[path] = cb || function() {}; } /* Record current hash, execute cb */ render() { this.currentUrl = window.location.hash.slice(1) || "/"; this.routes[this.currentUrl](); } } RouterClass.init(); const ContentDom = document.querySelector(".content-div"); const changeContent = content => (ContentDom.innerHTML = content); Router.route("/", () => changeContent("Default page")); Router.route("/page1", () => changeContent("page1 page")); Router.route("/page2", () => changeContent("page2 page")); </script> </html>
History mode
The history interface allows you to manipulate the browser's session history that has been accessed in tabs or frames. You can refer to the following two articles for the description of history
https://css-tricks.com/using-the-html5-history-api/
https://developer.mozilla.org/zh-CN/docs/Web/API/History
The following describes the api to be used in this mode
history basic api
- history.go(n): how many steps does the route jump? N is 2, 2 pages forward, - 2 pages backward
- history.back(): route backward, equivalent to history.go(-1). Users can click the back button in the upper left corner of the browser to simulate this method
- history.forward(): route forward, equivalent to history.go(1). Users can click the forward button in the upper left corner of the browser to simulate this method
pushState()
history.pushState(): add a route history. If cross domain URL is set, an error will be reported
history.pushState is used to add a history in browsing history, but does not trigger a jump. This method takes three parameters, one is:
State: a state object related to the specified URL. When the popstate event is triggered, the object will pass in a callback function. If this object is not needed, null can be filled here.
Title: the title of the new page, but all browsers currently ignore this value, so null can be filled here.
url: the new url must be in the same domain as the current page. The address bar of the browser will display this url.
popstate event of window
The popstate event is triggered when the activity history entry changes. If the activated history entry is created by a call to history.pushState(), or affected by a call to history.replaceState(), the state property of the popstate event contains a copy of the state object of the history entry.
Note that calling history.pushState() or history.replaceState() does not trigger the popstate event. This event is triggered only when the browser action is made, such as the user clicking the browser's rollback button (or calling history.back() in the Javascript code).
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>Document</title> </head> <body> <ul> <li><a href="/">/</a></li> <li><a href="/page1">page1</a></li> <li><a href="/page2">page2</a></li> </ul> <div class="content-div"></div> </body> <script> class RouterClass { constructor(path) { this.routes = {}; // Record cb corresponding to path identifier history.replaceState({ path }, null, path); // get into the state this.routes[path] && this.routes[path](); window.addEventListener("popstate", e => {// Triggered when the user clicks forward or backward of the browser console.log(e.state) const path = e.state && e.state.path; this.routes[path] && this.routes[path](); }); } /* Initialization */ static init() { window.Router = new RouterClass(location.pathname); } /* Register routes and callbacks */ route(path, cb) { this.routes[path] = cb || function() {}; } /* Jump route and trigger route corresponding callback */ go(path) { history.pushState({ path }, null, path); console.log(history); this.routes[path] && this.routes[path](); } } RouterClass.init(); const ul = document.querySelector("ul"); const ContentDom = document.querySelector(".content-div"); const changeContent = content => (ContentDom.innerHTML = content); Router.route("/", () => changeContent("Default page")); Router.route("/page1", () => changeContent("page1 page")); Router.route("/page2", () => changeContent("page2 page")); ul.addEventListener("click", e => { console.log(e.target.tagName); if (e.target.tagName === "A") { e.preventDefault(); Router.go(e.target.getAttribute("href")); } }); </script> </html>
Comparison between Hash mode and History mode
Hash mode uses the hash of URL to simulate a complete URL, so when the URL changes, the page will not be overloaded. History mode will directly change the URL, so some address information will be lost when the route jumps, and static resources will not be matched when the route address is refreshed or accessed directly. Therefore, you need to configure some information on the server to add a candidate resource covering all situations, such as jump index.html and so on
Advantages and disadvantages of hash routing
-
Advantage
- Simple implementation and good compatibility (compatible to ie8)
- Most front-end frameworks provide hash routing
- No server-side setup and development required
- No requests other than resource loading and ajax requests are made
-
shortcoming
- For some operations that need redirection, the backend can not get part of the hash content, resulting in the background unable to get the data in url. A typical example is the oauth verification of WeChat public number.
- The server cannot accurately track the front-end routing information
- The demand for anchor function will conflict with the current routing mechanism
Advantages and disadvantages of History(browser) routing
-
Advantage
- Parameters in the url will not be lost during redirection. The backend can get this part of data
- Most of the previous frameworks provide the routing implementation of browser
- The back end can accurately track route information
- You can use history.state to get the status information corresponding to the current url
-
shortcoming
- Less compatible than hash routing (only compatible to IE10)
- Need back-end support, return html document every time
Reference articles
Example source: https://segmentfault.com/a/1190000018081475
Official documents: https://developer.mozilla.org/en-US/docs/Web/API/History_API
Comparison of advantages and disadvantages: https://juejin.im/post/5b5ec5...