React and Vue both have hashRouters and routers. This article is used to clarify the concept of routing and implement hash routing and history routing using native JS. The article ends with a brief introduction to the History object and window.history to help you understand the History route.

The React – the router making warehouse

Vue – the router making warehouse

1, an overview of the

In previous multi-page applications (MPA), routing was mainly implemented by the back end and the entire page had to be refreshed every time new content was accessed, which put performance pressure on the server and affected the user experience on the browser.

Therefore, in the existing front-end practice, the solution of front-end routing appears. Front-end routing makes it necessary to refresh part of the page instead of the whole page when obtaining new content, which constitutes the concept of the so-called single page application (SPA).

In the existing front-end frameworks, there are two front-end routing implementation schemes: HashRouter and Router. HashRouter is based on hashchange event (known as Hash routing). The Router is implemented based on the history object (known as the history route);

The hash route is displayed as a URL with a fragment identifier (#) on the browser.

The history route appears in the browser as a URL that matches the URL of a normal remote resource (without anchor #).

2. Native implementation of hash routing

Features of hash urls:

  • Urls with anchor points (#);
  • When the HASH URL changes, the browser does not send a request to the server, and the page is not redirected.
  • Using window.location.hash, you can retrieve and modify the fragment identifier of the current page URL.

The basic roadmap for implementing hash routing is as follows:

  • throughhashchangeEvents listen for hash route changes;
  • Get server resources through XHR objects;
  • Render resources to the page;

Native JS implementation of hash routing:

1. HTML file:

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta  name="viewport" content="width=device-width, > <title>Hash Router and history Router implementation </title> <style>. Navbar {display:flex; justify-content:space-around; margin:0 auto; width:300px; } #container { width:400px; text-align: center; margin:0 auto; margin-top:100px; } </style> </head> <body> <div class="navbar"> <div class="navbar_item"><a href="#/page1" class="page1">page1</a></div> <div class="navbar_item"><a href="#/page2" class="page2">page2</a></div> <div class="navbar_item"><a href="#/page3" class="page3">page3</a></div> </div> <div id="container"></div> <script type="text/javascript" src="./hashRouter.js"></script> </body> </html>Copy the code


2. JS file:

// hashRouter.js class HashRouter { constructor(dom) { this.hashObj = {}; window.addEventListener('hashchange',(e) => { const path = e.newURL.split('#')[1]; if(path && this.hashObj[path]) { const data = this.getData(path); this.appendData(dom,data); } // Register the route (... paths){ paths.forEach(path => { this.hashObj[path] = path; GetData (path) {const url = `https://www.fastmock.site/mock/ba90f15d75470108ed2c56997636d24d/routertest/api/getPage${path.slice(-1)}data` let xhr = new XMLHttpRequest(); xhr.open('GET',url,false) xhr.send(null); if(xhr.status === 200) { return JSON.parse(xhr.responseText); } return {}; } // render data appendData(dom,data) {let containerDom = document.querySelector(dom); containerDom.innerHTML = ''; let titleDom = document.createElement('h1'); titleDom.innerText = data.title; let dataDom = document.createElement('div'); dataDom.innerText = data.data; let descDom = document.createElement('div'); descDom.innerText = data.desc; containerDom.append(titleDom,dataDom,descDom); } } const hashRouter = new HashRouter('#container'); hashRouter.register('/page1','/page2','/page3');Copy the code


Data interface used (built by FastMock)

  • www.fastmock.site/mock/ba90f1…
  • www.fastmock.site/mock/ba90f1…
  • www.fastmock.site/mock/ba90f1…


3. Native implementation of the history route

Common remote resource URL features:

  • There is no fragment identifier on the URL (#);
  • When the URL changes, the browser sends a request to the server to rerender the entire page;
  • throughwindow.historyThe History object to which the browser displays the changing URL is controlled.

The basic idea of implementing the history route:

  • Listen for the click event on the A TAB to access the normal URL, and block the default event to prevent the page from jumping and rerendering;
  • throughwindow.historyChange the URL displayed by the browser;
  • Get server resources through XHR objects;
  • Render resources to the page;

Native JS implementation of the history route:

1. HTML files

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta  name="viewport" content="width=device-width, < span style>. Navbar {display:flex; justify-content:space-around; margin:0 auto; width:300px; } #container { width:400px; text-align: center; margin:0 auto; margin-top:100px; } </style> </head> <body> <div class="navbar"> <div class="navbar_item"><a href="/page1" class="page1">page1</a></div> <div class="navbar_item"><a href="/page2" class="page2">page2</a></div> <div class="navbar_item"><a href="/page3" class="page3">page3</a></div> </div> <div id="container"></div> <script type="text/javascript" src="./historyRouter.js"></script> </body> </html>Copy the code


2. JS files

// historyRouter.js
class HistoryRouter {
​
    constructor(dom) {
        this.historyObj = {};
        this.dom = dom;
        
        this.listenClickOfa();
        this.listenPopstate();
​
    }
​
    // 注册路由
    register(...paths){
​
        paths.forEach(path => {
            this.historyObj[path] = path;
        })
        
    }
​
    // 监听 a 标签上的click 事件
    listenClickOfa() {
​
        window.addEventListener('click',(e) => {
            const path = e.target.href;
            
            if(e.target.tagName === 'A' && path && this.historyObj['/' + path.split('/').slice(-1)]) {
                e.preventDefault();
​
                // 变换 URL
                this.updateUrl(path);
​
                // 获取数据
                const data = this.getData(path);
​
                // 渲染数据
                this.appendData(data);
​
            }
            
            
        })
    }
​
    // 获取数据
    getData(path) {
        const url = `https://www.fastmock.site/mock/ba90f15d75470108ed2c56997636d24d/routertest/api/getPage${path.slice(-1)}data`
        let xhr = new XMLHttpRequest();
        xhr.open('GET',url,false)
        xhr.send(null);
        if(xhr.status === 200) {
​
            return JSON.parse(xhr.responseText);
        }
        return {};
    }
​
    // 通过 window.history 切换浏览器显示的 url
    updateUrl(path) {
        window.history.pushState({path:path},null,path);
    }
​
​
    // 渲染数据
    appendData(data) {
       
        let containerDom = document.querySelector(this.dom);
        containerDom.innerHTML = '';
​
        let titleDom = document.createElement('h1');
        titleDom.innerText = data.title; 
        let dataDom = document.createElement('div');
        dataDom.innerText = data.data;
        let descDom = document.createElement('div');
        descDom.innerText = data.desc;
​
        containerDom.append(titleDom,dataDom,descDom);
    }
​
​
​
    // 通过监听 popstate 事件来处理 history.back()或 history.forward() 时的页面渲染
    listenPopstate() {
​
        window.addEventListener('popstate',e => {
            const data = this.getData(e.state.path);
            this.appendData(data);
        })
    }
​
}
​
​
const historyRouter = new HistoryRouter('#container');
historyRouter.register('/page1','/page2','/page3');
Copy the code

This article code repository: gitee.com/hotpotliuyu…








The following is a brief introduction to the History object and window.history;

4. History object

1, an overview of the

The window.history property points to a history object that represents the browsing history of the current window; The historical URL is set to invisible by the browser for security reasons, and only three properties, length, scrollRestoration and State, are displayed.

window.history instanceof History; // true
Copy the code

The structure of the History object is as follows:

History {length: 5, // This History object has 5 URL histories, but the browser hides it by default scrollRestoration: "auto", state: {URL: "66666"}, [[Prototype]]: History }Copy the code

2, attributes,

  1. History.length

  2. History.state

    PushState () and history.replacestate () to change the state;

Eg:

Window.history. length // How many pages are visited by the current window Window.history. state // The current state of the history object of the current windowCopy the code

3, methods,

  1. History.forward

  2. History.back

  3. History.go

    Eg:

    window.history.forward(); // The current window moves to the previous page window.history.back(); // Move the current window to the next page window.history.go(num); // The current window jumps to the specified pageCopy the code
  4. History.pushState(state,title,url)

    Use to add a record to the History object.

    • State: A state object associated with the added record, which becomes the state property in the History object;
    • Title: Modifies the title property of a document, but most browsers today don’t use this parameter;
    • Url: page URL;

    Eg:

    window.history.pushState({url:'http://hotpotliu.com'},null,'http://hotpotliu.com')
    Copy the code
  5. History.replaceState(state,title,url)

    Used to modify the current record of the History object (the top page of the History object);

4. Popstate event

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

The browser does not fire the PopState event when the page first loads;

Eg:

window.addEventListener('popstate',function(event){ console.log(event.state); // event.state is the state of the corresponding URL in the history object; This can be modified with history.pushstate () and history.replacestate ()})Copy the code






References:

  • Ruan Yifeng ES5 History object