1. What is routing

Routing displays different content or pages based on different URLS

In the early days of routing, the back end directly reload the page according to the URL, that is, the back end controls the routing.

Later, the page became more and more complex, and the server was under more and more pressure. With the advent of Ajax (asynchronous refresh technology), the page realized non-reload to refresh data, so that the front end could control url self-management, and the front end routing was born from this.

Single-page applications are implemented because of the concept of front-end routing.

2. Two implementation principles of front-end routing

1 the Hash routing

We often see # in URLS, and there are two cases of #. One is the so-called anchor point, such as the typical back to the top button principle, jump between headings on Github, etc. The # in routing is not called anchor point, we call it hash, and most routing systems of large frameworks are implemented by hash.

We need an event that fires based on listening hash changes — the Hashchange event

The Window object provides an onHashChange event to listen for changes in the hash value, which is triggered whenever the hash value in the URL changes.

When we handle hash changes with window.location, instead of rerendering the page, we add it to the history as a new page, so that we jump to the page and register Ajax in the Hashchange event to change the page content.

window.addEventListener('hashchange'.function() {<! Here you can write the code you need -->});Copy the code

2 the History routing

HTML5’s History API adds an extension to the browser’s global History object.

Focus on the two new apis history.pushState and history.replaceState

Both apis accept three arguments, respectively

State Object – A JavaScript object associated with a new history entry created using the pushState() method. The popState event is emitted whenever the user navigates to a newly created state, and the state property of the event object contains a copy of the state object for the history entry.

Title – This parameter is currently ignored by FireFox, although it may be used in the future. Considering that this method may be modified in the future, it is safer to pass an empty string. Alternatively, you can pass in a short heading indicating the state to be entered.

Address (URL) – The address of the new history entry. The browser does not load the address after calling pushState(), but it may try to load it later, for example when the user restarts the browser. The new URL is not necessarily an absolute path; If it is a relative path, it will be based on the current URL; The incoming URL should be homologous with the current URL; otherwise, pushState() throws an exception. This parameter is optional; If not specified, the current URL of the document.

We’re typing on the console

window.history.pushState(null, null, "https://www.baidu.com/?name=lvpangpang");

You can see the change in the browser URL

Note: the URL here does not support cross-domain, such as you enter the above code in a non-Baidu domain name.

I used this mode in Vue or React and found that when I refreshed the page, it would go to the moon.

The reason is that the url in history mode is a real URL. The server searches the file path of the URL for resources and returns 404 if no resource is found. In layman’s terms, this pattern is recognized by the server and processed accordingly.

There are a number of solutions to this 404 problem.

A Configuring webpack (Development Environment)

historyApiFallback:{
    index:'/index.html'//index.html Template.html created for the current directory}Copy the code

B Configuring NgniX (Production environment)

location /{
    root   /data/nginx/html;
    index  index.html index.htm;
    error_page 404 /index.html;
}
Copy the code

3. The routing demo

The following steps will explain how to write a front-end route.

This is the process of turning our knowledge into skills.

As we have seen above, routing displays different content or pages based on different URLS. For front-end routing, different content is displayed according to different URLS.

Hence the following version of the 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>
<div id="root">
  <a href="#/index"> </a> <a href="#/list"> list </a> </div> <script> const root = document.querySelector('#root');
window.onhashchange = function (e) {
  var hash = window.location.hash.substr(1);
  if(hash= = ='/index') {
    root.innerHTML = 'This is the index component.';
  }
  if (hash= = ='/list') {
    root.innerHTML = 'This is the list component';
  }
}
</script>
</body>
</html>
Copy the code

The above is only a small demo, to give us the most intuitive sense of front-end routing. This time, in order to have a better effect, specially introduced GIF.

4. Route JS version

Looking at the demo, if you can’t wait to implement a route, let’s implement it step by step. I’ll give him a name here — Purgatory, mostly for the purposes of the rest of the story.

4.1 Parameter configuration of Purgatory

Here I copy the route configuration in VUE and React. The default configuration is an array of route objects.

// const routes = [{path:'/index',
  url: 'js/index.js'
}, {
  path: '/list',
  url: 'js/list.js'
}, {
  path: '/detail',
  url: 'js/detail.js'
}];
var router = new Router(routes);
Copy the code

The routing configuration is similar to vUE and React. A component is also a JS file. A component contains HTML, CSS, and JS, which will eventually be compiled into a JS file.

4.1 The overall framework of Purgatory

function Router(opts = []) {
  
}
Router.prototype = {
  init: function() {}, // route register initRouter:function() {}, // parses the url to getParamsUrl:function() {}, // route handler urlChange:function() {}, // render view (execute matching js code) render:function(currentHash) {}, // Single route registration map:function(item) {}, // before switching beforeEach:function(callback) {}, // afterEach:function(callback) {}, // route async lazy load JS file asyncFun:function (file, transition) {
    
  }

}
Copy the code

4.1 The interior of Purgatory

The overall code framework for Purgatory has been outlined above, so let’s write each function.

A. the init function

This is how the Inferno plugin will perform when called, of course, to register the route and bind the corresponding route switching event.

init() { var oThis = this; // register route this.initRouter(); // Page load matches route window.addeventListener ('load'.function() { oThis.urlChange(); }); // Route switch window.addeventListener ('hashchange'.function() { oThis.urlChange(); }); }}Copy the code

B initRouter function + Map function

To register a route, the function is to initialize the route object array parameters at the time of route matching, such as /index route corresponding to /js/index.js.

// Route register initRouter:function() { var opts = this.opts; opts.forEach((item, index) => { this.map(item); }); } // Single route registration map:function (item) {
    path = item.path.replace(/\s*/g, ' '); / / filter space enclosing routers/path = {callback: (transition) = > {returnthis.asyncFun(item.url, transition); }, // callback fn: null // cache corresponding js file}}Copy the code

These. Routers store route objects. Callback functions for each route are used to load the corresponding JS file.

The FUNCTION of fn function in each Router object is the js file that has been loaded, which can be used for multiple times during route switching.

C asyncFun function

This function loads the target JS file asynchronously. The principle is to dynamically insert pages using manually generated javascript tags. Of course, before loading the actual JS file, you need to make a judgment that the target JS has been loaded.

// Load js file asyncFun:function(file, transition) { // console.log(transition); var oThis = this, routers = this.routers; // Check whether the cache is removedif (routers[transition.path].fn) {
      oThis.afterFun && oThis.afterFun(transition)
      routers[transition.path].fn(transition)
    } else {
      var _body = document.getElementsByTagName('body') [0]; var scriptEle = document.createElement('script');
      scriptEle.type = 'text/javascript';
      scriptEle.src = file;
      scriptEle.async = true;
      SPA_RESOLVE_INIT = null;
      scriptEle.onload = function() { oThis.afterFun && oThis.afterFun(transition) routers[transition.path].fn = SPA_RESOLVE_INIT; routers[transition.path].fn(transition) } _body.appendChild(scriptEle); }}Copy the code

D render function

As you can see from the name, the main function of this function is to render the page, in this case to perform load route corresponding JS file. A judgment is made that if there is a routing daemon, the route will walk by the daemon.

// render view (execute matching js code)function(currentHash) { var oThis = this; // Global routing daemonif (oThis.beforeFun) {
      oThis.beforeFun({
        to: {
          path: currentHash.path,
          query: currentHash.query
        },
        next: functionOthis. routers[currenthash.path].callback.call(oThis, currentHash)}} oThis. }else{ oThis.routers[currentHash.path].callback.call(oThis, currentHash); }}Copy the code

E beforeEach function

Route daemons (vUE – Router global route daemons) do some things like login permissions.

// before switching beforeEach:function (callback) {
    if (Object.prototype.toString.call(callback) === '[object Function]') {
      this.beforeFun = callback;
    } else {
      console.trace('Please pass in parameters of function type'); }},Copy the code

Okay, so that’s the main code for Purgatory, and now we can see how that works.