What is routing? What are the front-end routes? What are their characteristics?

Routing is the ability to display different pages or content based on different URLS, a concept that was first introduced by the back end. The back end did this before, and when we visited xxx.abc.com/xx, the general flow could be imagined as follows:

  1. The browser makes a request to the server.
  2. The server listens on port 80, and if a request comes in, it parses the URL.
  3. The server configures the route to the client and then returns the appropriate information (such as HTML strings, JSON data, or images).
  4. The browser determines how to parse the data based on the packet’s Content-type.

The above is the most initial implementation of back-end routing, so since there is back-end routing, why do we need front-end routing? One of the major disadvantages of back-end routing is that the page needs to be refreshed every time the route is switched, and then the Ajax request is made, and the request data is returned, so refreshing the page every time the route is switched is not good for the user experience. So in order to improve the user experience, our front-end routing was created. It solves the problem that the browser won’t refresh.

The DOM Window object provides access to the browsing History of the current session through the History object. Html4 had the following methods:

Window.history. length: Returns the number of pages viewed by the current session.

window.history.go(? Delta): accepts an integer as a parameter to move based on the current page’s position in the session browsing history. If the parameters are 0, undefined, null, or false, the page will be refreshed, equivalent to the window.location.reload() method. If the parameter is greater than the number of browser views, or less than the number before viewing, nothing is done.

Window.history.back (). Move to the previous page. Equivalent to clicking the browser’s back button, equivalent to window.history.go(-1);

Window.history.forward (). Moving to the next page is equivalent to clicking the browser’s forward button, equivalent to window.history.go(1).

In HTML5, the History API adds the ability to manipulate session browsing History. Here are some new methods:

Window.history.state. This parameter is read-only and represents the state object associated with the current record of the session’s browsing history. As shown below:

window.history.pushState(data, title, ? Url): Adds a record to the session browsing history.

window.history.replaceState(data, title, ? Url): This method is similar to history.pushState, but it means that it will modify the current record of the session browsing history rather than add a new record. That is, the current browser address is changed to the address after replaceState, but the total length of the browser history is not increased.

Note: The URL address changes after the above two methods are executed. But the page is not refreshed. So with that in mind, let’s take a look at front-end routing.

Front-end routing also has two modes, the first is hash mode and the second is history mode. Let’s take a look at these two knowledge points and their differences as follows:

1. The hash pattern

The hash routing mode is as follows: xxx.abc.com/#/xx. There are hash signs, followed by changes in the hash value. If you change the hash value, it will not make a request to the server and therefore will not refresh the page. And every time the hash value changes, the Hashchange event is emitted. So we can listen to the event to see what changes have been made to the hash value. For example, we can simply monitor:

function hashAndUpdate() {// todo matcheshash} window.addeventListener ()'hashchange'.hashAndUpdate);
Copy the code

Let’s look at the attributes of a location:

Href // The protocol of the current URL, including :; Such as HTTPS: Location. protocol /* The host name and port number, if the port number is 80(HTTP) or 443(HTTPS), then the port number will be omitted, such as www.baidu.com:8080 */ location.host // the host name: for example: www.baidu.com location.hostname // port number; For example, 8080 location.port // the path part of the URL, starting with /; For example https://www.baidu.com/s?ie=utf-8, then pathName ='/s'Location. pathname // query parameters from? Start; For example https://www.baidu.com/s?ie=utf-8 then search ='? ie=utf-8'
location.search

// hashIs a fragment in the page from#/a/b
location.hash 

Copy the code

location.href

Href = location.href = location. Href = location. Href = location.

location.hash

Changing the hash does not trigger a page jump, because the hash link is a fragment of the current page, so if the hash changes, the page will scroll to where the hash is attached. However, if there is no hash fragment in the page, it has no effect. Like the A link. This is similar to the window.history.pushState method, which changes the URL without refreshing the page. You can also see that the operation does not refresh the URL, as shown below:

Comparing hash and pushState has the following disadvantages:

  1. Hash can only modify part of the url fragment identifier. It must start with #, but pushState and can modify the path, query parameters, and fragment identifiers. PushState is a more elegant way to access a front-end route than hash (because it doesn’t have hash signs).

  2. Hash must be different from the original value in order to add a session browsing history, but pushState can add the same URL, as shown below:

1.1 Use the hashchange event to listen for URL hash changes

To demonstrate this, we use Node to start a service. Then we have an index.html page, which imports a js file with the following js code:

window.addEventListener('hashchange'.function(e) {
  console.log(e)
});
Copy the code

The above code is to listen for the event that the hash value changes, and then we access the index.html page, and then in the console, do the following operations, as shown in the figure below:

As can be seen above; Whether we change the hash value directly through the Location interface, or whether we move forward or backward through history (changing the hashchange), we can see that we can listen for changes to the URL hash through the hashchange event. The page is not refreshed.

2. The history mode

HTML5’s History API adds this extension to the browser’s global History object. It is an interface of a browser that provides an onPopState event in the Window object to listen for changes in the history stack. This event is triggered whenever information in the history stack changes. The following events are provided:

window.addEventListener('popstate'.function(e) {
  console.log(e)
});
Copy the code

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

history.pushState(data[,title][,url]); // Appends a record to history

history.replaceState(data[,title][,url]); // Replace the current page in history.

The above two methods are new to HTML5, and they can also change the URL without refreshing the page. We can also do a demo to listen for the popState event, and now put the following js code in my JS:

window.addEventListener('popstate'.function(e) {
  console.log(e)
});
Copy the code

Then we visit the page, which looks like this:

As shown in the figure above, methods like location.hash, history.go(-1), and history.pushState trigger popState events and the browser URL changes accordingly. Only the URL is changed and the page is not refreshed.

Features of hash mode:

In hash mode, the url in the browser address bar has hash numbers like #, such as (http://localhost:3001/#/a). # is not passed to the server, which means the page is not refreshed. The page is not reloaded during route switching.

History mode features:

Browser addresses do not have #, such as (http://localhost:3001/a); It doesn’t refresh the page either. But the URL will change.

How to implement a simple hash route?

**** Hash routes must meet the following basic conditions:

  1. Changing the hash value in the URL does not reload the page.
  2. Changing the hash value adds a record to the browser’s access history. You can switch the hash value using the browser’s back and forward buttons.
  3. Using the Hashchange event, we can listen for changes in the hash value to load different page displays.

There are two methods for triggering hash changes:

The first is to set the href attribute using the A tag. When you click on the A tag, the address bar changes and the hashchange event is triggered. For example, link A:

<a href="#/test1"> testhash1</a>
Copy the code

The second is to assign directly to location.hash via js, which also changes the URL and triggers the Hashchange event.

location.hash = '#/test1';
Copy the code

So we can implement a simple demo with the following HTML code:

<! DOCTYPE html> <html lang="en">
<head>
  <meta charset="UTF-8">
  <title>hash</title> </head> <body> <ul> <li><a href="# /"</a></li> <li><a href="#/a"</a></li> <li><a href="#/b"</a></li> </ul> </body> </ HTML >Copy the code

Then our hash.js code looks like this:


class HashRouter {
  constructor() {// storehashThis.routes = {}; // Save the currenthash
    this.currentHash = ' '; // Bind event consthashChangeUrl = this.hashChangeUrl.bind(this); // Page loading event window.adDeventListener ('load'.hashChangeUrl, false); // Listen for the hashchange event window.addeventListener ('hashchange'.hashChangeUrl, false); } / / path path and match them with the callback function, and use the above this. Routes stored the route (path, the callback) {enclosing routes/path = callback | |function() {};
  }
  hashChangeUrl() {/* Get the currenthashThe value location.hash retrieves the value:"# / a, so the location. The hash. Slice (1) = '/ a * / enclosing currentHash = location. The hash. Slice (1) | |'/'. This.routes [this.currenthash](); } // Initialize const Router = new HashRouter(); const body = document.querySelector('body'); const changeColor = function(color) { body.style.backgroundColor = color; }; The Router / / registered function. The route ('/', () = > {changeColor (" red "); }); Router.route('/a', () => { changeColor('green'); }); Router.route('/b', () => { changeColor('#CDDC39'); });Copy the code

There is a route function in the js code. This function initializes the route and binds it to the function, and stores it in the this.routes object. The hashchange event is then used to listen, and if it is fired, the route is found and the corresponding function is fired. We click on an A link to call the corresponding function, or we can use location.hash = ‘/b’ in the console; To change the value will also trigger.

Three: How to implement a simple history route? *

<! DOCTYPE html> <html lang="en">
<head>
  <meta charset="UTF-8">
  <title>hash</title> </head> <body> <ul> <li><a href="/"</a></li> <li><a href="/a"</a></li> <li><a href="/b"</a></li> </ul> </body> </ HTML >Copy the code
class HistoryRoutes {
  constructorThis.routes = {}; // Listen for the popState event window.adDeventListener ('popstate', (e) => { const path = this.getState(); this.routes[path] && this.routes[path](); }); } // Get the routing pathgetState() {
    const path = window.location.pathname;
    return path ? path : '/';
  }
  route(path, callback) {
    this.routes[path] = callback || function() {};
  }
  init(path) {
    history.replaceState(null, null, path);
    this.routes[path] && this.routes[path]();
  }
  go(path) {
    history.pushState(null, null, path);
    this.routes[path] && this.routes[path]();
  }
}

window.Router = new HistoryRoutes();
console.log(location.pathname);
Router.init(location.pathname);

const body = document.querySelector('body');

const changeColor = function(color) { body.style.backgroundColor = color; }; // register the function router.route ('/', () => {
  changeColor('red');
});
Router.route('/a', () => {
  changeColor('green');
}); 
Router.route('/b', () => {
  changeColor('#CDDC39');
}); 

const ul = document.querySelector('ul');
ul.addEventListener('click', e => {
  console.log(e.target);
  if (e.target.tagName === 'A') {
    e.preventDefault();
    Router.go(e.target.getAttribute('href')); }});Copy the code

Four: Hash and history routes are implemented together

. | - demo project | | - babelrc# Solve es6 syntax problems
|  |--- node_modules   # all dependent packages
|  |--- dist           # Packaged page to access this page use: http://0.0.0.0:7799/dist
|  |--- js
|  | |--- base.js      
|  | |--- hash.js
|  | |--- history.js
|  | |--- routerList.js
|  | |--- index.js
|  |--- package.json   # dependent package file
|  |--- webpack.config.js # webpack packs files
|  |--- index.html    # HTML page
Copy the code

The index.html page is as follows:

<! DOCTYPE html> <html lang="en">
<head>
  <meta charset="UTF-8">
  <title>hash+historyRoute demo</title> </head> <body> <div id="app"></div>
  <ul class="list">
    <li><a href="/"</a></li> <li><a href="/hash"> I amhashPage </a></li> <li><a href="/history"> I amhistoryPage </ A ></li> </ul> </body> </ HTML >Copy the code

The js/base.js code is as follows:

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

export class BaseRouter {
  constructor(list) {
    this.list = list;
  }
  render(state) {
    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

The base.js class is a constructor that is inherited in hash.js or history.js, so both hash and history classes have the Render () method. The Render method finds out if the corresponding path is the same as the Path in the RouterList, and if so, assigns the contents of the corresponding Component to the element whose ID is app. The js/hash.js code is as follows:

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

// hashThe route inherits from BaseRouterexportclass HashRouter extends BaseRouter { constructor(list) { super(list); this.handler(); / / to monitorhashEvent changes and rerenders the page window.addeventListener ('hashchange', (e) => { this.handler(); }); } / / renderinghandler() { const state = this.getState(); this.render(state); } // Get the currenthash
  getState() {
    const hash = window.location.hash;
    return hash ? hash.slice(1) : '/'; } // Get the full url getUrl(path) {const href = window.location.href; const index = href.indexOf(The '#');
    const base = index > -1 ? href.slice(0, index) : href;
    return `${base}#${path}`;} / /hashIf the value changes, implement push(path) {window.location.hash = path; } // Replace (path) {window.location.replace(this.geturl (path)); } go(n) {window.history.go(n); }}Copy the code

The hash code is shown above; This class contains the HashChange event that listens for changes in the hash value, and if it changes, the handler function is called. Before executing the render method in this function, the getState method is called, which is used to retrieve the current hash. For example, the getState method using location.hash would fetch a hash like ‘#/x’ and return ‘/x’. The getUrl() method is to get the full URL. The other methods are push,replace, and go.

The js/history.js code is as follows:

import { BaseRouter } from './base.js';
exportclass HistoryRouter extends BaseRouter { constructor(list) { super(list); this.handler(); // Listen for historical stack changes and re-render the page window.adDeventListener ('popstate', (e) => { this.handler(); }); } / / renderinghandler() { const state = this.getState(); this.render(state); } // Get the routing pathgetState() {
    const path = window.location.pathname;
    return path ? path : '/'; } /* pushState does not trigger a popState event. */ push(path) {window.history.pushState(null, null, path); this.handler(); } /* replaceState does not trigger a popState event. So we need to manually call handler function * / replace (path) {window. History. ReplaceState (null, null, path); this.handler(); } go(num) { window.history.go(num); }};Copy the code

The code is similar to the hash.js code.

The js/ RouterList.js code is as follows:

export const ROUTERLIST = [
  {
    path: '/',
    name: 'index',
    component: 'This is the home page'
  },
  {
    path: '/hash',
    name: 'hash',
    component: 'This is a hash page'
  },
  {
    path: '/history',
    name: 'history',
    component: 'This is the history page.'
  },
  {
    path: The '*',
    component: '404 pages'}];Copy the code

The js/index.js code is as follows:

import { HashRouter } from './hash';
import { HistoryRouter } from './history';
import { ROUTERLIST } from './routerList'; // Routing mode, the default ishash
const MODE = 'history';

class WebRouter {
  constructor({ mode = 'hash', routerList }) {
    this.router = mode === 'hash'? new HashRouter(routerList) : new HistoryRouter(routerList); } push(path) {// return this.router so there ishashorhistoryThe push method in this.router.push(path); } replace(path) { this.router.replace(path); } go(num) { this.router.go(num); } } const webRouter = new WebRouter({ mode: MODE, routerList: ROUTERLIST }); document.querySelector('.list').addEventListener('click', e => {
  const event = e || window.event;
  event.preventDefault();
  if (event.target.tagName === 'A') {
    const url = event.target.getAttribute('href'); ! url.indexOf('/')? webRouter.push(url) : webRouter.go(url); }});Copy the code

This is all of the code above. If you look closely, you can see the benefit of writing this code. The hash.js and history.js code are separated and have corresponding methods, and then initialize the corresponding code in index.js. Finally, the DOM click event is used to pass the corresponding route in.

pit

1. I have been working on the H5 code for some time, but I encountered a “god pit” yesterday. The History object in Html has different effects in different browsers.

2. Problem Description:

1) When window.history.go(-1) is called to implement the page return, the network request of the "target page" is actually reloaded on different browsers (resending the network request instead of reading the browser's cache when the network request is sent). 2) When window.history.go(-2) is called to return two levels, the iPhone safari will "reload "(re-send the web request but not actually send the web request but read the browser's cache), but will need to actually refresh the target page after returning to the" destination page ".Copy the code

3. Solutions:

1) At the beginning, the idea is to record the data when leaving the "target web page", and add and subtract the data when go(-2) returns to the "target web page". 2) The real problem is that when I return to the "target page", I will read the browser cache, trace back to the source, I think of the Ajax method to send the network request, is there a configuration that does not cache the request? $.ajax() contains the following configuration options :cache When cache is true, the request and its response will be cached. False does not cache. This solves the problem of reading the cache when you return to the "target page".Copy the code

4. Part of code display:

$.ajax({

	type: "post",
	cache: false,
	timeout: 60000,
	url: url,
            .......Copy the code