Home >Web Front-end >H5 Tutorial >Detailed analysis of the core history library in React Router

Detailed analysis of the core history library in React Router

不言
不言Original
2018-08-14 11:01:563121browse

This article introduces to you a detailed analysis of the core history library in React Router. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.

Preface

Use React to develop slightly more complex applications. React Router is almost the only choice for routing management. Although React Router has undergone four major version updates and its functions have become more and more abundant, no matter how it changes, its core dependence on the history library has not changed. Let's take a look at what functions this library with 4k stars on github provides.

HTML5 history object

Talking about the history library, do you think this word is a bit familiar? Yes, a new history object with the same name has also been added to the HTML5 specification. Let's take a look at what problems this history object is used to solve.

In the era when jQuery dominated the front-end, updating pages through ajax requests without refreshing was a very popular page processing method at that time. The prototype of SPA evolved at that time. In order to mark changes to the page so that the correct page elements can still be displayed after refreshing, the hash value of the URL is generally changed to uniquely locate the page. But this brings up another problem: users cannot use forward/backward to switch pages.

In order to solve this problem, the history object came into being. When the URL or hash of the page changes, the browser will automatically push the new URL into the history object. A state array is maintained inside the history object to record changes in the URL. When the browser performs a forward/backward operation, it actually calls the corresponding method of the history object (forward/back) to retrieve the corresponding state to switch pages.

In addition to operating the URL, the history object also provides two methods that can update the internal state without operating the URL, namely pushState and replaceState. You can also store additional data in the state, and then retrieve it through event.state in the onpopstate event. If you want a deeper understanding of the history object, you can refer to here, and here.

The relationship between the history library and the HTML5 history object

Let’s go back and look at the history library. It essentially does the following 4 things:

  1. Learns from the concept of HTML5 history object, and extends some functions based on it

  2. Provides 3 types of history: browserHistory, hashHistory, memoryHistory, and maintains a unified API

  3. Supports the publish/subscribe function. When the history changes, the subscription function can be automatically triggered

  4. Provides practical functions such as jump interception, jump confirmation and basename

Let’s compare some similarities and differences between the two APIs. The following is the history library:

const history = {
    length,        // 属性,history中记录的state的数量
    action,        // 属性,当前导航的action类型
    location,      // 属性,location对象,封装了pathname、search和hash等属性
    push,          // 方法,导航到新的路由,并记录在history中
    replace,       // 方法,替换掉当前记录在history中的路由信息
    go,            // 方法,前进或后退n个记录
    goBack,        // 方法,后退
    goForward,     // 方法,前进
    canGo,         // 方法,是否能前进或后退n个记录
    block,         // 方法,跳转前让用户确定是否要跳转
    listen         // 方法,订阅history变更事件
  };

The following is the HTML5 history object:

const history = {
    length,         // 属性,history中记录的state的数量
    state,          // 属性,pushState和replaceState时传入的对象
    back,           // 方法,后退
    forward,        // 方法,前进
    go,             // 方法,前进或后退n个记录
    pushState,      // 方法,导航到新的路由,并记录在history中
    replaceState    // 方法,替换掉当前记录在history中的路由信息
}

// 订阅history变更事件
window.onpopstate = function (event) {
    ...
}

It can be seen from the comparison that the relationship between the two is very close. The history library can be said to be the history object. A superset of , which is a more powerful history object.

createHashHistory source code analysis

Below, we take one of the three history types, hashHistory, as an example to analyze the history source code and see what it does. Let’s first look at how it handles hash changes.

// 构造hashHistory对象
const createHashHistory = (props = {}) => {
    ...
    const globalHistory = window.history;    // 引用HTML5 history对象
    ...
    // transitionManager负责控制是否进行跳转,以及跳转后要通知到的订阅者,后面会详细讨论
    const transitionManager = createTransitionManager();
    ...
    // 注册history变更回调的订阅者
    const listen = listener => {
        const unlisten = transitionManager.appendListener(listener);
        checkDOMListeners(1);

        return () => {
            checkDOMListeners(-1);
            unlisten();
        };
    };
    
    // 监听hashchange事件
    const checkDOMListeners = delta => {
        listenerCount += delta;

        if (listenerCount === 1) {
            window.addEventListener(HashChangeEvent, handleHashChange);
        } else if (listenerCount === 0) {
            window.removeEventListener(HashChangeEvent, handleHashChange);
        }
    };
    
    // hashchange事件回调
    const handleHashChange = () => {
        ...
        // 构造内部使用的location对象,包含pathname、search和hash等属性
        const location = getDOMLocation();    
        ...
        handlePop(location);
    };
    
    // 处理hash变更逻辑
    const handlePop = location => {
        ...
        const action = "POP";
        // 给用户展示确认跳转的信息(如果有的话),确认后通知订阅者。如果用户取消跳转,则回退到之前状态
        transitionManager.confirmTransitionTo(location, action, getUserConfirmation, ok => {
            if (ok) {
                setState({action, location});    // 确认后通知订阅者
            } else {
                revertPop(location);             // 取消则回退到之前状态
            }
        });
    };
    
    // 更新action,location和length属性,并通知订阅者
    const setState = nextState => {
        Object.assign(history, nextState);

        history.length = globalHistory.length;

        transitionManager.notifyListeners(history.location, history.action);
    };
    ...
}

The above is the logic for handling passive hash changes. In one sentence, it can be summarized as follows: subscribe to the hash change event, determine whether the change is really needed, update your own attributes if a change is needed, and notify the subscriber. If no change is needed, Go back to the previous state.

Let’s take a look at what the transitionManager does, focusing on the content related to publishing/subscribing, and ignoring the content related to the user confirmation jump.

const createTransitionManager = () => {
    ...
    // 内部维护的订阅者列表
    let listeners = [];

    // 注册订阅者
    const appendListener = fn => {
        let isActive = true;

        const listener = (...args) => {
            if (isActive) fn(...args);
        };

        listeners.push(listener);

        return () => {
            isActive = false;
            listeners = listeners.filter(item => item !== listener);
        };
    };

    //通知订阅者
    const notifyListeners = (...args) => {
        listeners.forEach(listener => listener(...args));
    };
    ...
}

The code here is clear at a glance, it is to maintain a list of subscribers and notify the relevant functions when the hash changes.

The above is the content related to passive update when hash changes. Let's take a look at the code related to active update. Taking push as an example, replace is similar.

const push = (path, state) => {
    ...
    const action = "PUSH";
    const location = createLocation(path, undefined, undefined, history.location);

    transitionManager.confirmTransitionTo(location, action, getUserConfirmation, ok => {
        if (!ok)     // 如果取消,则不跳转
            return;
        ...
        pushHashPath(encodedPath);        // 用新的hash替换到url当中
        ...
        setState({action, location});     // 更新action,location和length属性,并通知订阅者

    });
};

// 用新的hash替换到url当中
const pushHashPath = path => (window.location.hash = path);

When the browser performs forward and backward operations, the history library is actually implemented by operating the HTML5 history object.

const globalHistory = window.history;

const go = n => {
    ...
    globalHistory.go(n);
};

const goBack = () => go(-1);

const goForward = () => go(1);

When window.history.go is called, the hash will change, triggering the hashchange event, and then the history library will notify the relevant subscribers of the change.

Summary

This article provides a more in-depth introduction to the history library that React Router core relies on. Starting from the new history object in HTML5, we compare its inextricable relationship with the history library, and use hashHistory as an example to analyze the implementation details of its code in detail.

Finally, let’s review what the history library has done:

  1. Learn from the concept of HTML5 history object and expand some functions on its basis

  2. Provides 3 types of history: browserHistory, hashHistory, memoryHistory, and maintains a unified API

  3. ##Supports publish/subscribe function, when the history changes At this time, the subscription function can be automatically triggered

  4. Provides practical functions such as jump interception, jump confirmation and basename

Although the history library is React Router's core dependency, but it has no dependency on React itself. If your project has a history manipulation scenario, you can also introduce it into the project.

Related recommendations:

How to use h5 to implement react drag and drop sorting components (with code)

How to solve the collapse problem of margin-top in HTML5 (Code attached)

What are the tags and common rules in HTML5? Introduction to html5 tags and rules


The above is the detailed content of Detailed analysis of the core history library in React Router. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn