溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

React Router中的核心history庫怎么用

發(fā)布時間:2021-06-15 11:22:47 來源:億速云 閱讀:251 作者:小新 欄目:web開發(fā)

這篇文章主要為大家展示了“React Router中的核心history庫怎么用”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學習一下“React Router中的核心history庫怎么用”這篇文章吧。

前言

使用React開發(fā)稍微復雜一點的應用,React Router幾乎是路由管理的唯一選擇。雖然React Router經(jīng)歷了4個大版本的更新,功能也越來越豐富,但無論怎么變,它的核心依賴history庫卻一直沒變。下面我們來了解下這個在github上有4k+星的庫到底提供了什么功能。

HTML5 history對象

聊到history庫,是不是覺得這個單詞有點熟悉?不錯,HTML5規(guī)范里面,也新增了一個同名的history對象。下面我們來看下這個history對象用來解決什么問題。

在jQuery統(tǒng)治前端的年代,通過ajax請求無刷新更新頁面是當時相當流行的頁面處理方式,SPA的雛形就是那時候演化出來的。為了標示頁面發(fā)生的變化,方便刷新后依然能顯示正確的頁面元素,一般會通過改變url的hash值來唯一定位頁面。但這會帶來另一個問題:用戶無法使用前進/后退來切換頁面。

為了解決這個問題,history對象應運而生。當頁面的url或者hash發(fā)生變化的時候,瀏覽器會自動將新的url push到history對象中。history對象內(nèi)部會維護一個state數(shù)組,記錄url的變化。在瀏覽器進行前進/后退操作的時候,實際上就是調(diào)用history對象的對應方法(forward/back),取出對應的state,從而進行頁面的切換。

除了操作url,history對象還提供2個不用通過操作url也能更新內(nèi)部state的方法,分別是pushStatereplaceState。還能將額外的數(shù)據(jù)存到state中,然后在onpopstate事件中再通過event.state取出來。如果希望對history對象作更深入的理解,可以參考 這里,和這里。

history庫與HTML5 history對象的關(guān)系

我們再回過頭來看history庫。它本質(zhì)上做了以下4件事情:

  1. 借鑒HTML5 history對象的理念,在其基礎上又擴展了一些功能

  2. 提供3種類型的history:browserHistory,hashHistory,memoryHistory,并保持統(tǒng)一的api

  3. 支持發(fā)布/訂閱功能,當history發(fā)生改變的時候,可以自動觸發(fā)訂閱的函數(shù)

  4. 提供跳轉(zhuǎn)攔截、跳轉(zhuǎn)確認和basename等實用功能

再對比一些兩者api的異同。以下是history庫的:

const history = {
    length,        // 屬性,history中記錄的state的數(shù)量
    action,        // 屬性,當前導航的action類型
    location,      // 屬性,location對象,封裝了pathname、search和hash等屬性
    push,          // 方法,導航到新的路由,并記錄在history中
    replace,       // 方法,替換掉當前記錄在history中的路由信息
    go,            // 方法,前進或后退n個記錄
    goBack,        // 方法,后退
    goForward,     // 方法,前進
    canGo,         // 方法,是否能前進或后退n個記錄
    block,         // 方法,跳轉(zhuǎn)前讓用戶確定是否要跳轉(zhuǎn)
    listen         // 方法,訂閱history變更事件
  };

以下是HTML5 history對象的:

const history = {
    length,         // 屬性,history中記錄的state的數(shù)量
    state,          // 屬性,pushState和replaceState時傳入的對象
    back,           // 方法,后退
    forward,        // 方法,前進
    go,             // 方法,前進或后退n個記錄
    pushState,      // 方法,導航到新的路由,并記錄在history中
    replaceState    // 方法,替換掉當前記錄在history中的路由信息
}

// 訂閱history變更事件
window.onpopstate = function (event) {
    ...
}

從對比中可以看出,兩者的關(guān)系是非常密切的,history庫可以說是history對象的超集,是功能更強大的history對象。

createHashHistory源碼分析

下面,我們以三種history類型中的一種,hashHistory為例,來分析下history的源碼,看看它都干了些什么。先看下它是怎么處理hash變更的。

// 構(gòu)造hashHistory對象
const createHashHistory = (props = {}) => {
    ...
    const globalHistory = window.history;    // 引用HTML5 history對象
    ...
    // transitionManager負責控制是否進行跳轉(zhuǎn),以及跳轉(zhuǎn)后要通知到的訂閱者,后面會詳細討論
    const transitionManager = createTransitionManager();
    ...
    // 注冊history變更回調(diào)的訂閱者
    const listen = listener => {
        const unlisten = transitionManager.appendListener(listener);
        checkDOMListeners(1);

        return () => {
            checkDOMListeners(-1);
            unlisten();
        };
    };
    
    // 監(jiān)聽hashchange事件
    const checkDOMListeners = delta => {
        listenerCount += delta;

        if (listenerCount === 1) {
            window.addEventListener(HashChangeEvent, handleHashChange);
        } else if (listenerCount === 0) {
            window.removeEventListener(HashChangeEvent, handleHashChange);
        }
    };
    
    // hashchange事件回調(diào)
    const handleHashChange = () => {
        ...
        // 構(gòu)造內(nèi)部使用的location對象,包含pathname、search和hash等屬性
        const location = getDOMLocation();    
        ...
        handlePop(location);
    };
    
    // 處理hash變更邏輯
    const handlePop = location => {
        ...
        const action = "POP";
        // 給用戶展示確認跳轉(zhuǎn)的信息(如果有的話),確認后通知訂閱者。如果用戶取消跳轉(zhuǎn),則回退到之前狀態(tài)
        transitionManager.confirmTransitionTo(location, action, getUserConfirmation, ok => {
            if (ok) {
                setState({action, location});    // 確認后通知訂閱者
            } else {
                revertPop(location);             // 取消則回退到之前狀態(tài)
            }
        });
    };
    
    // 更新action,location和length屬性,并通知訂閱者
    const setState = nextState => {
        Object.assign(history, nextState);

        history.length = globalHistory.length;

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

以上就是處理被動的hash變更的邏輯,一句話概括就是:訂閱hash變更事件,判斷是否確實要變更,如需變更則更新自己的屬性,通知訂閱者,不需變更則回退到之前的狀態(tài)。

下面再看下transitionManager做了什么,重點看發(fā)布/訂閱相關(guān)內(nèi)容,忽略用戶確認跳轉(zhuǎn)相關(guān)內(nèi)容。

const createTransitionManager = () => {
    ...
    // 內(nèi)部維護的訂閱者列表
    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));
    };
    ...
}

這里的代碼一目了然,就是維護一個訂閱者列表,當hash變更的時候通知到相關(guān)的函數(shù)。

以上是hash改變的時候被動更新相關(guān)的內(nèi)容,下面再看下主動更新相關(guān)的代碼,以push為例,replace大同小異。

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

    transitionManager.confirmTransitionTo(location, action, getUserConfirmation, ok => {
        if (!ok)     // 如果取消,則不跳轉(zhuǎn)
            return;
        ...
        pushHashPath(encodedPath);        // 用新的hash替換到url當中
        ...
        setState({action, location});     // 更新action,location和length屬性,并通知訂閱者

    });
};

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

在瀏覽器進行前進后退操作時,history庫實際上是通過操作HTML5 history對象實現(xiàn)的。

const globalHistory = window.history;

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

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

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

當調(diào)用window.history.go的時候,hash會發(fā)生變化,進而觸發(fā)hashchange事件,然后history庫再將變更通知到相關(guān)的訂閱者。

總結(jié)

本文對React Router核心依賴history庫進行了比較深入的介紹。從HTML5新增的history對象講起,對比了它跟history庫千絲萬縷的關(guān)系,并以hashHistory為例子詳細分析了其代碼的實現(xiàn)細節(jié)。

最后,我們再來回顧一下history庫做了哪些事情:

  1. 借鑒HTML5 history對象的理念,在其基礎上又擴展了一些功能

  2. 提供3種類型的history:browserHistory,hashHistory,memoryHistory,并保持統(tǒng)一的api

  3. 支持發(fā)布/訂閱功能,當history發(fā)生改變的時候,可以自動觸發(fā)訂閱的函數(shù)

  4. 提供跳轉(zhuǎn)攔截、跳轉(zhuǎn)確認和basename等實用功能

雖然history庫是React Router的核心依賴,但它跟React本身并沒有依賴關(guān)系。如果你的項目中有操作history的場景,也可以將其引入到項目中來。

以上是“React Router中的核心history庫怎么用”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學習更多知識,歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI