preface

As we all know, as the business functions of front-end applications become more and more complex, users have higher and higher requirements for the use of experience, single side (SPA) has become the mainstream form of front-end applications. One of the most significant features of large single-page applications is the use of front-end routing to jump rotor page system, by changing the URL of the page, without re-requesting the page, update the page view.

Update the view but the browser not to render the page, just to render molecular page, loading speed, flexible page response, this is the advantage of the SPA, this also is the front-end routing principle of core, it will give a person a kind of the same feeling as if to operate APP, to achieve this function in the browser environment there are two main ways:

  • usingURLhash(#)
  • usingH5The new methodHistory interface

usingURLtheHash(#)

Before H5 became popular, spas generally used the HASH (#) of the URL as the anchor point, obtained the value after #, monitored its change, and rendered the corresponding sub-page. Netease cloud music official website is the use of this technology.

For example, if your address is http://localhost:8888/#/abc, the output from the location.hash would be #/ ABC.

So I’m going to start with the location object.

Let’s take a look at the official properties of location

attribute describe
hash Sets or returns the URL starting with # (anchor)
host Sets or returns the hostname and port number of the current URL
hostname Sets or returns the host name of the current URL
href Sets or returns the full URL
pathname Sets or returns the path portion of the current URL
port Sets or returns the port number of the current URL
protocol Sets or returns the protocol for the current URL
search Set or return from? The URL section at the beginning

As can be seen from the table above, we can easily obtain the part after #, so how can we monitor the change of this part and change the corresponding sub-page?

The window object has an event that listens for hash changes. That event is onHashChange. First we need to listen for this event:

<body>
  <h1 id="id"></h1>
  <a href="#/id1">id1</a>
  <a href="#/id2">id2</a>
  <a href="#/id3">id3</a>
</body>

<script>
  window.addEventListener('hashchange', e => {
    e.preventDefault()
    document.querySelector('#id').innerHTML = location.hash
  })
</script>
Copy the code

You can see that we are completely listening for the URL change, and the content on the page has changed accordingly. There are three ways to load different pages:

  • Find the node content and change it (which is what we demonstrated above)
  • importaJSFiles, inside filesexportTemplate string
  • usingAJAXLoad the correspondingHTMLtemplate

I’ve already shown you the first way, but it’s too limited, so I’ll show you two other ways to load a page.

importway

Define a JS file called demo1.js and enter the content:

const str = '
      
I'm importing JS file
'
export default str Copy the code

Import it from the main file and test it (always open it on the server for Chrome, or open it directly from Firefox) :

<body>
  <h1 id="id"></h1>
  <a href="#/id1">id1</a>
  <a href="#/id2">id2</a>
  <a href="#/id3">id3</a>
</body>
<! Type ="module" -->
<script type="module">
  import demo1 from './demo1.js'
  document.querySelector('#id').innerHTML = demo1
  window.addEventListener('hashchange', e => {
    e.preventDefault()
    document.querySelector('#id').innerHTML = location.hash
  })
</script>
Copy the code

You can see that the import file is already in effect, and most frameworks are treated in a similar way after being compiled.

For example, in the Vue framework, a.vue file is a custom file type that describes a VUE component using htML-like syntax. Each.vue file contains three types of top-level language blocks

AJAXway

This article explains the routing mechanism in detail. AJAX uses JQuery as a direct wheel.

Define an HTML file named demo2.html and write something to it (since the main page already has root tags such as head and body, this file only needs to write the tags that need to be replaced) :

<div>I'm an HTML file that AJAX loaded in</div>
Copy the code

We’ll write to the main file and test it:

<body>
  <h1 id="id"></h1>
  <a href="#/id1">id1</a>
  <a href="#/id2">id2</a>
  <a href="#/id3">id3</a>
</body>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<script type="module">
  // import demo1 from './demo1.js'
  // document.querySelector('#id').innerHTML = demo1
  $.ajax({
    url: './demo2.html'.success: (res) = > {
      document.querySelector('#id').innerHTML = res
    }
  })
  window.addEventListener('hashchange', e => {
    e.preventDefault()
    document.querySelector('#id').innerHTML = location.hash
  })
</script>
Copy the code

As you can see, the files loaded in using AJAX have also taken effect.

Now that loading the contents of the different pages has taken effect, we just need to wrap our listeners and use observer mode to encapsulate the route changes:

<body>
  <h1 id="id">I'm a blank page</h1>
  <a href="#/id1">id1</a>
  <a href="#/id2">id2</a>
  <a href="#/id3">id3</a>
</body>
<script type="module">
  import demo1 from './demo1.js'
  // Create a newRouter class
  class newRouter {
    // Initializes the routing information
    constructor() {
      this.routes = {};
      this.currentUrl = ' ';
    }
    // Pass in the URL and the corresponding callback function based on the URL
    route(path, callback = (a)= >{{})this.routes[path] = callback;
    }
    // Cut the hash, render the page
    refresh() {
      this.currentUrl = location.hash.slice(1) | |'/';
      this.routes[this.currentUrl] && this.routes[this.currentUrl]();
    }
    / / initialization
    init() {
      window.addEventListener('load'.this.refresh.bind(this), false);
      window.addEventListener('hashchange'.this.refresh.bind(this), false); }}// new a Router instance
  window.Router = new newRouter();
  // Route instance initialization
  window.Router.init();

  // Get key nodes
  var content = document.querySelector('#id');

  Router.route('/id1', () => {
    content.innerHTML = 'id1'
  });
  Router.route('/id2', () => {
    content.innerHTML = demo1
  });
  Router.route('/id3', () => {
    $.ajax({
      url: './demo2.html'.success: (res) = > {
        content.innerHTML = res
      }
    })
  });
</script>
Copy the code

The effect is as follows:

So far, using hash(#) for front-end routing management has been implemented.

usingH5The new methodHistory interface

The hash method used above is good, but the problem is that it is ugly. If it is used in wechat or other apps that do not display urls, it is fine, but if it is used in regular browsers, it will be a problem.

Therefore, the H5 History mode solves this problem.

Prior to H5, History only had the following apis:

API instructions
back() Go back to the last visitURL(Same as when the browser hits the back button)
forward() Advancing to the point of retractingURL(Same as when the browser clicks the forward button)
go(n) nReceives an integer and moves to the page specified by the integer, for examplego(1)The equivalent offorward().go(-1)The equivalent ofback().go(0)Equivalent to refresh the current page

If you move beyond the bounds of the access history, the above three methods do not fail, but silently fail.

However, in the era of the H5, the new H5 brought more new features:

From the cache

By default, the browser caches the current session page so that when the next page hits the back button, or the previous page hits the forward button, the browser extracts and loads the page from the cache, a feature known as “round-trip caching.”

PS: This cache preserves page data, DOM, and JS state, essentially leaving the entire page intact.

Add record to history stack: pushState(state, title, URL)

Browser support: IE10+

  • State: aJSObject (no larger than 640kB), used in thepopstateIs retrieved as a parameter in the event. If you don’t need this object, you can fill it in herenull
  • Title: The title of the new page. Some browsers (such as Firefox) ignore this parameter, so it is generallynull
  • Url: the address of the new history,It can be a page address or an anchor point valueThe newurlMust be related to the currenturlIn the same field, otherwise an exception will be thrown. If not specified, this parameter will be set to the current documenturl

Chestnut:

// 现在是 localhost/1.html
const stateObj = { foo: 'bar' };
history.pushState(stateObj, 'page 2'.'2.html');

// The browser address bar will immediately become localhost/2.html
// But!!
// Does not jump to 2.html
// Does not check if 2.html exists
// Will not be retrieved in popState events
// No page refresh is triggered

// This method simply adds the latest record
Copy the code

In addition, there are a few points to note:

  • willurlDoes not fire when set to the anchor point valuehashchange
  • If set according to the same Origin policydifferentDomain addresses will report errors. The purpose of this is to prevent users from thinking they are the same site, which is easy to do without this limitationXSSCSRFAnd other attack modes

Change the current history: replaceState(state, title, URL)

Browser support: IE10+

  • Parameter Meaning The same aspushstate
  • Change the current history rather than add new records
  • It doesn’t trigger eitherpopstate

history.state

Browser support: IE10+

  • Of the current historystate.

popstate

Definition: The PopState event is emitted every time the browsing history (that is, the history object) of the same document changes.

Note: This event is not triggered by calling the pushState or replaceState methods alone, but only when the user clicks the browser’s back and forward buttons, or calls the back, forward, or Go methods using JavaScript. In addition, this event is only for the same document and will not be triggered if a switch in browsing history results in a different document being loaded.

Chestnut:

window.onpopstate= (event) = >{ &emsp; &emsp;console.log(event.state) // State object for the current history
}
Copy the code

implementation

With that in mind, let’s implement routing in History mode!

We will modify the above HTML slightly, please be patient analysis:

<body>
  <h1 id="id">I'm a blank page</h1>
  <a class="route" href="/id1">id1</a>
  <a class="route" href="/id2">id2</a>
  <a class="route" href="/id3">id3</a>
</body>
Copy the code
import demo1 from './demo1.js'
  // Create a newRouter class
  class newRouter {
    // Initializes the routing information
    constructor() {
      this.routes = {};
      this.currentUrl = ' ';
    }
    route(path, callback) {
      this.routes[path] = (type) = > {
        if (type === 1) history.pushState( { path }, path, path );
        if (type === 2) history.replaceState( { path }, path, path );
        callback()
      };
    }
    refresh(path, type) {
      this.routes[this.currentUrl] && this.routes[this.currentUrl](type);
    }
    init() {
      window.addEventListener('load', () = > {// Get the current URL path
        this.currentUrl = location.href.slice(location.href.indexOf('/'.8))
        this.refresh(this.currentUrl, 2)},false);
      window.addEventListener('popstate', () = > {this.currentUrl = history.state.path
        this.refresh(this.currentUrl, 2)},false);
      const links = document.querySelectorAll('.route')
      links.forEach((item) = > {
        // Override the click event of the a tag to prevent the default jump behavior
        item.onclick = (e) = > {
          e.preventDefault()
          // Get the modified URL
          this.currentUrl = e.target.getAttribute('href')
          / / rendering
          this.refresh(this.currentUrl, 2)}})}}// new a Router instance
  window.Router = new newRouter();
  // Instance initialization
  window.Router.init();

  // Get key nodes
  var content = document.querySelector('#id');

  Router.route('/id1', () => {
    content.innerHTML = 'id1'
  });
  Router.route('/id2', () => {
    content.innerHTML = demo1
  });
  Router.route('/id3', () => {
    $.ajax({
      url: './demo2.html'.success: (res) = > {
        content.innerHTML = res
      }
    })
  });
Copy the code

The demo diagram is as follows:

conclusion

In general, hash and history will work, unless you’re more concerned with the appearance level, and the # symbol does look a little ugly in the URL. In addition, calling history.pushState() has the following advantages over modifying hash directly, according to Mozilla Develop Network:

  • pushState()Set a newURLCan be with the currentURLHomologous arbitraryURL; whilehashCan only be modified#The following section, therefore can only be set with the currentURLWith the documentURL
  • pushState()Set a newURLCan be associated with the currentURLExactly the same, this will also add records to the stack; whilehashThe new value set must be different from the original to trigger the action to add the record to the stack
  • pushState()throughstateObjectParameters can add any type of data to a record; whilehashOnly short strings can be added;
  • pushState()Additional Settings availabletitleProperty for later use.

History mode is a happy alternative to hash mode, but history is not always a good alternative to hash mode. It works well in a browser, but when it comes to making HTTP requests from the URL back end, the difference is significant. This is especially true when the user manually enters the URL and hits the press enter, or when the browser is refreshed (or restarted).

  • hashIn mode, onlyhashThe content before the symbol is included in the request, for examplehttp://www.qqq.com, so for the back end, even if the full coverage of the route is not achieved, there is no return404Error.
  • historyIn mode, front-endURL Must beAnd the actual backend initiating the requestURLConsistent, such ashttp://www.qqq.com/book/id. If the back end lacks a pair/book/idRoute processing will be returned404Error.Vue-RouterThe official website says: “However, this mode to play well, but also need to support the background configuration… So, you add a candidate resource on the server that covers all cases: ifURLIf no static resource is matched, the same should be returnedindex.htmlThe page, the page is youappDependent pages.”
  • The back-end (ApacheNginx) for simple route configuration, and with front-end routes404Page support.

Finally, I am sorry to promote the component library I wrote based on the Taro framework: MP-Colorui.

I will be very happy if I can star easily. Thank you.

Click here for documentation

Click here to get the GitHUb address