With the popularity of Ajax, the asynchronous data request experience has greatly improved, allowing the user to interact with the page without refreshing the browser, and a more advanced version of the asynchronous interaction experience is SPA, a single page application.

Single-page application is not only refresh-free in page interaction, but also refresh-free in page hopping. In order to achieve single-page application, front-end routing is used.

There are two common patterns

Similar to the way that the server route resolves the corresponding URL path and returns the corresponding page/resource, the front-end route is also very simple to implement. It is to match different URL paths, parse, and then dynamically render the regional HTML content.

This causes a page refresh every time the natural URL changes.

So in the case of changing the URL, how to ensure that the page is not refreshed?

Hash pattern

Prior to 2014, routing was implemented with hashes, and URL hashes looked something like:

https://www.xxx.com/#/login
Copy the code

This hash change does not cause the browser to make a request to the server, and the browser will not refresh the page without making a request.

Why does changing hash not refresh the page? -The hash ‘#’ of the URL

The ‘#’ represents a location in the web page, and the character after it is the identifier of that location. It is only useful to the browser, the server does not recognize it, so HTTP requests do not contain the #

(To request a URL that contains #, partially escape using encodeURIComponent())

Changing the hash will only scroll the browser to the appropriate location, not reload the page

Every time the hash value changes, a Hashchange event is triggered. By listening for this event, window.onhashchange we can detect the changed hash value and perform corresponding page operations.

Simple and easy to implement

Let’s implement the hash pattern in the simplest code, just to get the idea (you can copy it directly to an HTML and view it from a static server like HTTP-server) :

<! 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 > Hash routing < / title > < / head > < body > < ul > < li > < a href ="#red"</a></li> <li><a href="#green"</a></li> <li><a href="#grey"</a></li> </ul> <script>function watchHash() {
        const hash = window.location.hash.slice(1) || '/';
        switch (hash) {
          case "red":
            document.body.style.background = "red";
            break;
          case "green":
            document.body.style.background = "green";
            break;
          case "grey":
            document.body.style.background = "grey";
            break;
        }
      }
      window.addEventListener("hashchange", watchHash, false);
      window.addEventListener("load", watchHash, false);
    </script>
  </body>
</html>
Copy the code

How to achieve the most basic forward and backward?

Here we simply implement the backward function, the forward idea is similar:

<! 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 > Hash routing < / title > < / head > < body > < ul > < li > < a href ="#red"</a></li> <li><a href="#green"</a></li> <li><a href="#grey"</a></li> </ul> <button ID ="back" disabled="true"</button> <script> var isGoBack =false;
      const history = [];
      function watchHash() {
        const hash = window.location.hash.slice(1) || "/"; // Prevent recording when backing uphash
        if(! isGoBack && window.location.hash) { history.push(window.location.hash); } back.disabled = history.length > 0 ?false : true;
        console.log(history);
        switch (hash) {
          case "red":
            document.body.style.background = "red";
            break;
          case "green":
            document.body.style.background = "green";
            break;
          case "grey":
            document.body.style.background = "grey";
            break;
          default:
            document.body.style.background = "#fff";
        }
        isGoBack = false;
      }
      back.onclick = goBack;
      function goBack() {
        isGoBack = true;
        if (history.length > 0) {
          history.pop(1);
          window.location.hash = history[history.length - 1] || "";
        } else {
          back.disabled = true;
        }
      }
      window.addEventListener("load", watchHash, false);
      window.addEventListener("hashchange", watchHash, false);
    </script>
  </body>
</html>
Copy the code

The idea is to record the hash value of each hashchange event in an array, and click back to retrieve the last hash value to override the current page’s hash.

Note that you need to distinguish whether the current hash is generated backward (hash changes should not be recorded when backward) or forward to avoid duplicate records.

The history mode

It can be seen that although front-end routing can be realized in the early hash mode, the backward forward operation is very troublesome.

Since 2014, HTML5 has introduced the History API, which gives us quick access to page History.

The history.pushState() and history.replacestate () methods, which add and modify history entries respectively, allow you to change urls without reloading the page.

There is also the popState event: Window.onpopstate allows you to listen for popState triggered by clicking the back and forward buttons in the browser (or by calling the history.back(), history.forward(), and history.go() methods in JavaScript) Events.

This allows you to implement front-end routing in a different way, but in the same way that you implement hash.

Implement the hash code above with history

<! DOCTYPE html> <html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0"/> <title>History Route </title> </head> <body> <ul ID ="ul">
      <li><a href="/red"</a></li> <li><a href="/green"</a></li> <li><a href="/grey"> gray background </a></li> </ul> <script> const path = window.location.pathname; history.replaceState({ path: path }, null, path); ul.addEventListener("click", (e) => {
        if (e.target.tagName === "A") {
          e.preventDefault();
          const path = e.target.getAttribute("href"); history.pushState({ path: path }, null, path); watchHistory(path); }});function watchHistory() {
        const path = window.location.pathname;
        switch (path) {
          case "/red":
            document.body.style.background = "red";
            break;
          case "/green":
            document.body.style.background = "green";
            break;
          case "/grey":
            document.body.style.background = "grey";
            break;
          default:
            document.body.style.background = "#fff";
        }
      }
      window.addEventListener("popstate", watchHistory, false);
    </script>
  </body>
</html>
Copy the code

With HTML5 implementations, single-page routing urls don’t have an extra #, making them more aesthetically pleasing.

But because there are no # numbers, the browser still sends a request to the server when the user does something like refresh the page.

To avoid this, the history mode requires server support to redirect all routes to the root page.

How do I listen for pushState and replaceState changes

We know from theory and practice that replaceState() and pushState() apis do not trigger popState listening events.

We can generate a new Window listener event to listen for changes:

function addListen(type) {
  const source = history[type];
  return function () {
    const event = new Event(type);
    event.arguments = arguments;
    window.dispatchEvent(event);
    return source.apply(this, arguments);
  };
}

history.pushState = addListen("pushState");
history.replaceState = addListen("replaceState");

window.addEventListener("replaceState", (e) => {
  console.log("I'm listening to the replaceState.");
});
window.addEventListener("pushState", (e) => {
  console.log(I'm listening to pushState);
});
Copy the code

Compare the two modes

  1. # less history mode is more natural
  2. The history mode must be IE9 or later, which is incompatible with Hash mode IE8
  3. The history mode requires server support, whereas the Hash mode does not support server rendering

conclusion

These are the main principles and implementation ideas of the hash and history modes of front-end routing. If you like them, don’t forget to like 😄!


Reference for this article:

Interviewer: Do you know anything about front-end routing?

Ali P7: Do you know routing?

Practice series front-end routing