In Web SPA, the front-end route describes the one-way mapping between URL and UI, that is, the CHANGE of URL causes the update of UI page (without refreshing the page).

The core problem

As mentioned above, in front-end routing, when the URL changes, we need to trigger the UI page update without refreshing the page. Therefore, when implementing front-end routing, we need to solve the following two core problems.

  • How do I detect if the URL has changed?
  • How can I change the URL without causing a page refresh?

We can answer both of these questions with Hash and History implementations.

  • In Hash mode, we can passhashchangeThe event listens for URL changes and is triggered in the following scenariohashchangeEvent: change URL by browser forward or backward, change URL by tag, passwindow.locationChange the URL. Hash is the Hash part of the URL, and changing the Hash part of the URL does not cause a page refresh.
  • In History mode, we can passpopstateEvents listen for URL changes. We can do this by callingpushStatereplaceStateTwo ways to change the URL without causing a page refresh. It is worth noting that this is triggered when changing the URL forward or backward through the browserpopstateEvent while passingpushState,replaceStateOr tag changing URL does not triggerpopstateEvent, so we need to manually intercept.

implementation

GitHub Gist front-end routing implementation

For testing purposes, we used github.com/zeit/serve as the page server, with the following commands.

Install dependencies.
yarn

Vanilla Hash
yarn vanilla.hash

# Vanilla History demo.
yarn vanilla.history
Copy the code

Vanilla

For now, we use native HTML/JS for both Hash and History modes of front-end routing, independent of any framework.

Hash

The code for the page vanilla. Hash. HTML is shown below.


      
<html lang="zh-CN">
  <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 Route - Vanilla</title>
  </head>
  <body>
    <ul>
      <! -- Define route -->
      <li><a href="#/home">home</a></li>
      <li><a href="#/about">about</a></li>

      <! Render route UI -->
      <div id="routeView"></div>
    </ul>
    <script src="hash.js"></script>
  </body>
</html>
Copy the code

In vanilla. Hash. js, we detect whether the URL has changed by listening for the Hashchange event. When the URL changes, we call the onHashChange function to update the page view without refreshing the page by modifying the innerHTML attribute of the element.

// Maintain the UI page.
let routerView = null;

// When the route changes, render corresponding UI pages according to the route.
function onHashChange() {
  switch (window.location.hash) {
    case ' ':
    case '#/home':
      routerView.innerHTML = 'Home';
      return;
    case '#/about':
      routerView.innerHTML = 'About';
      break;
    default:}}// The hashchange event will not be triggered after the page is loaded.
window.addEventListener('DOMContentLoaded', () => {
  routerView = document.querySelector('#routeView');
  onHashChange();
});

// Listen for route changes.
window.addEventListener('hashchange', onHashChange)
Copy the code

History

The code for the page vanilla. History. HTML is shown below.


      
<html lang="zh-CN">
  <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>History Route - Vanilla</title>
  </head>
  <body>
    <ul>
      <! -- Define route -->
      <li><a href="/home">home</a></li>
      <li><a href="/about">about</a></li>

      <! Render route UI -->
      <div id="routeView"></div>
    </ul>
    <script src="vanilla.history.js"></script>
  </body>
</html>
Copy the code

In vanilla.history.js, we detect URL changes by listening for popState events. When the URL changes, we call the onPopState function to update the page view without refreshing the page by modifying the innerHTML attribute of the element.

Since changing urls with pushState, replaceState, and tags does not trigger a popState event, we need to manually intercept it.

// Maintain the UI page.
let routerView = null;

// When the route changes, render corresponding UI pages according to the route.
function onPopState() {
  switch (window.location.pathname) {
    case '/':
    case '/home':
      routerView.innerHTML = 'Home';
      return;
    case '/about':
      routerView.innerHTML = 'About';
      break;
    default:}}// The hashchange event will not be triggered after the page is loaded.
window.addEventListener('DOMContentLoaded', () => {
  routerView = document.querySelector('#routeView');
  // Refresh the page.
  onPopState();

  // Intercepts the default behavior of  tag click events, using pushState to modify the URL and update the manual UI, thus implementing the effect of clicking links to update the URL and UI.
  const links = document.querySelectorAll('a[href]');
  links.forEach(el= >
    el.addEventListener('click'.function handler(e) {
      e.preventDefault();
      // Manually intercept.
      window.history.pushState(null.' ', el.getAttribute('href')); onPopState(); })); });// Listen for route changes.
window.addEventListener('popstate', onPopState);
Copy the code

When using History mode, a 404 page appears when we refresh the page. To solve this problem, we need the rewrite request.

In Nginx, we need to add the following setting: for all requests, we respond to the same page and let the page take over the routing.

location / {
  try_files $uri $uri/ /vanilla.history.html;
}
Copy the code

In serve, we simply define the Rewrites array.

{
  "rewrites": [{"source": "* *"."destination": "vanilla.history.html"}}]Copy the code

The resources

  • Amazing front-end routing principle analysis and implementation