What is routing?

The concept of routing originally appeared in the back end. When the front and back ends were not separated, the back end controlled the routing. The server received the request from the client, resolved the corresponding URL path, and returned the corresponding page/resource.

Simply put, routing is to display different content or pages according to different URLS.

Source of the front-end route

Once upon a time, users had to refresh the page every time they updated it, which affected the interactive experience very much. Later, to solve this problem,Ajax (asynchronous loading scheme) brought great improvement to the experience.

Although Ajax solves the pain points of user interaction, jumping between multiple pages can also have a bad experience, so spa(single-page Application) use was born. The SPA application is based on front-end routing, so there is front-end routing.

Today’s popular Vue-router/React-router is also based on the principle of front-end routing

Two principles of front-end routing

1. The Hash pattern

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

window.onhashchange = function(){
    
    // The hash value is changed
    
    // do you want
}
Copy the code

2. The History mode

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

Simply put,history is an interface to the browser’s history stack. I’m not going to go through every API in History. See the portal for details

The Window object provides an onPopState event to listen for changes in the history stack, which is triggered when the history stack information changes.

It is important to note that calling history.pushState() or history.replacestate () does not trigger the PopState event. This event is triggered only when a browser action is made.

window.onpopstate = function(){
    // History stack information changed
    // do you want
}

Copy the code

History provides two apis for manipulating the history stack :history.pushState and history.replacestate

history.pushState(data[,title][,url]);// Append a record to the history
Copy the code
history.replaceState(data[,title][,url]);// Replace the current page in the history.
Copy the code
// data: a JavaScript object associated with a new history entry created with pushState(). The PopState event is triggered whenever the user navigates to a newly created state, and the state property of the event object contains a copy of the historical entry's state object.

// Title: This parameter is currently ignored by FireFox, although it may be used later. Given the possibility of future changes to the method, it is safer to pass an empty string. Alternatively, you can pass in a short title indicating the state you are going to enter.

//url: the address of the new history entry. The browser does not load the address after calling the pushState() method, but it may attempt to do so later, for example when the user restarts the browser. The new URL does not have to be an absolute path; If the path is relative, it will be based on the current URL; The passed URL should be the same as the current URL, otherwise pushState() will throw an exception. This parameter is optional; If not specified, the current URL of the document.
Copy the code

The advantages and disadvantages of the two models

contrast Hash History
ornamental The ugly beauty
compatibility >ie8 >ie10
practical Direct use of The rear end is required
The namespace The same document homologous

Build (cao) a simple front-end route

This demo is intended to help us further understand the concept of front-end routing through practice, so it is only a simple implementation

The history mode 404

When we use history mode, if not configured, a 404 appears when refreshing the page.

The reason is that the URL in the history mode is the real URL, and the server will search for the resource in the file path of the URL. If no resource is found, the server will return 404.

In this demo, we use the historyApiFallback property in webpack-dev-server to support HTML5 History Mode.

File structure

|-- package.json
|-- webpack.config.js
|-- index.html
|-- src
    |-- index.js
    |-- routeList.js
    |-- base.js
    |-- hash.js
    |-- history.js
Copy the code

1. Set up the environment

No more nonsense, directly on the code ~

package.json

{
  "name": "web_router"."version": "1.0.0"."description": ""."main": "index.js"."scripts": {
    "dev": "webpack-dev-server --config ./webpack.config.js"
  },
  "author": "webfansplz"."license": "MIT"."devDependencies": {
    "html-webpack-plugin": "^ 3.2.0"."webpack": "^ 4.28.1"."webpack-cli": "^ 3.2.1." "."webpack-dev-server": "^ 3.1.14"}}Copy the code

webpack.config.js

'use strict';

const path = require('path');

const webpack = require('webpack');

const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  mode: 'development'.entry: './src/index.js'.output: {
    filename: '[name].js'
  },
  devServer: {
    clientLogLevel: 'warning'.hot: true.inline: true.open: true.// Useful when developing single-page applications. It relies on the HTML5 History API. If set to true, all jumps will point to index.html (resolve Histroy Mode 404).
    historyApiFallback: true.host: 'localhost'.port: '6789'.compress: true
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new HtmlWebpackPlugin({
      filename: 'index.html'.template: 'index.html'.inject: true}})];Copy the code

2. Open lu

First let’s initialize and define the functions and configuration parameters we need to implement.

The front-end routing parameter methods
Mode (mode) Push (pumped)
routeList The replace (replace)
Go (forward/backward)

src/index.js


const MODE=' ';

const ROUTELIST=[];

class WebRouter {
  constructor() { } push(path) { ... } replace(path) { ... } go(num) { ... }}new WebRouter({
  mode: MODE,
  routeList: ROUTELIST
});

Copy the code

Earlier we said that front-end routing can be implemented in two ways.

1. Define the route list

2. We create corresponding classes for these two methods respectively, and instantiate them according to different mode parameters to complete the implementation of class webRouter.

src/routeList.js


export const ROUTELIST = [
  {
    path: '/'.name: 'index'.component: 'This is index page'
  },
  {
    path: '/hash'.name: 'hash'.component: 'This is hash page'
  },
  {
    path: '/history'.name: 'history'.component: 'This is history page'
  },
  {
    path: The '*'.name: 'notFound'.component: '404 NOT FOUND'}];Copy the code

src/hash.js

export class HashRouter{}Copy the code

src/history.js

export class HistoryRouter{}Copy the code

src/index.js

import { HashRouter } from './hash';
import { HistoryRouter } from './history';
import { ROUTELIST } from './routeList';
// Routing mode
const MODE = 'hash';  

class WebRouter {
  constructor({ mode = 'hash', routeList }) {
    this.router = mode === 'hash' ? new HashRouter(routeList) : new HistoryRouter(routeList);
  }
  push(path) {
    this.router.push(path);
  }
  replace(path) {
    this.router.replace(path);
  }
  go(num) {
    this.router.go(num); }}const webRouter = new WebRouter({
  mode: MODE,
  routeList: ROUTELIST
});

Copy the code

Now that we’ve implemented webRouter’s function, let’s do it two ways.

Because both modes need to call a method to refresh the different routing content,so~

index.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>The front-end routing</title>
  </head>
  <body>
    <div id="page"></div>
  </body>
</html>

Copy the code

js/base.js


const ELEMENT = document.querySelector('#page');

export class BaseRouter {
 //list = route list
  constructor(list) {
    this.list = list;
  }
  render(state) {
   // Match the current route. If not, use 404 to configure the content and render ~
    let ele = this.list.find(ele= > ele.path === state);
    ele = ele ? ele : this.list.find(ele= > ele.path === The '*'); ELEMENT.innerText = ele.component; }}Copy the code

Ok, let’s implement the two patterns.

Hash pattern

src/hash.js


import { BaseRouter } from './base.js'; 

export class HashRouter extends BaseRouter {
  constructor(list) {
    super(list);
    this.handler();
    // Listen for hash changes, and rerender the hash changes
    window.addEventListener('hashchange', e => {
      this.handler();
    });
  }
  / / rendering
  handler() {
    this.render(this.getState());
  }
  // Get the current hash
  getState() {
    const hash = window.location.hash;
    return hash ? hash.slice(1) : '/';
  }
  // Get the full URL
  getUrl(path) {
    const href = window.location.href;
    const i = href.indexOf(The '#');
    const base = i >= 0 ? href.slice(0, i) : href;
    return `${base}#${path}`;
  }
  // Change the hash value to implement the push function
  push(path) {
    window.location.hash = path;
  }
  // Use location.replace to implement the replacement function
  replace(path) {
    window.location.replace(this.getUrl(path));
  }
  // Use the history mode go method to simulate the forward/backward function
  go(n) {
    window.history.go(n); }}Copy the code

The History mode

src/history.js

import { BaseRouter } from './base.js';

export class HistoryRouter extends BaseRouter {
  constructor(list) {
    super(list);
    this.handler();
    // Listen for changes in the history stack information, and re-render when changes occur
    window.addEventListener('popstate', e => {
      this.handler();
    });
  }
  / / rendering
  handler() {
    this.render(this.getState());
  }
  // Obtain the routing path
  getState() {
    const path = window.location.pathname;
    return path ? path : '/';
  }
  // Use the pushState method to implement the push function
  PushState does not trigger popState, so you need to call the render function manually
  push(path) {
    history.pushState(null.null, path);
    this.handler();
  }
  // Use replaceState to implement the replacement function
  //replaceState does not raise popState events, so you need to call the render function manually
  replace(path) {
    history.replaceState(null.null, path);
    this.handler();
  }
  go(n) {
    window.history.go(n); }}Copy the code

3. Little success

In this way, a simple front-end route is completed.

The source address

If you think it helps you, give a star ha ~