首頁 > web前端 > H5教程 > React Router中的核心history函式庫的詳細分析

React Router中的核心history函式庫的詳細分析

不言
發布: 2018-08-14 11:01:56
原創
3162 人瀏覽過

這篇文章要跟大家介紹的內容是關於React Router中的核心history庫的詳細分析,有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。

前言

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

HTML5 history物件

聊到history函式庫,是不是覺得這個字有點熟?不錯,HTML5規格裡面,也新增了一個同名的history物件。下面我們來看看這個history物件用來解決什麼問題。

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

為了解決這個問題,history物件應運而生。當頁面的url或hash發生變化的時候,瀏覽器會自動將新的url push到history物件中。 history物件內部會維護一個state數組,記錄url的變化。在瀏覽器進行前進/後退操作的時候,實際上就是呼叫history物件的對應方法(forward/back),取出對應的state,從而進行頁面的切換。

除了操作url,history物件也提供2個不用透過操作url也能更新內部state的方法,分別是pushStatereplaceState。也可以將額外的資料存到state中,然後在onpopstate事件中再透過event.state取出。如果希望對history物件作更深入的理解,可以參考 這裡,和這裡。

history函式庫與HTML5 history物件的關係

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

  1. 借鑒HTML5 history物件的概念,在其基礎上又擴展了一些功能

  2. 提供3種類型的history:browserHistory,hashHistory,memoryHistory,並保持統一的api

  3. 支援發布/訂閱功能,當history發生改變的時候,可以自動觸發訂閱的函數

  4. 提供跳躍攔截、跳轉確認和basename等實用功能

#再比較一些兩者api的異同。以下是history函式庫的:

1

2

3

4

5

6

7

8

9

10

11

12

13

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变更事件

  };

登入後複製

以下是HTML5 history物件的:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

const history = {

    length,         // 属性,history中记录的state的数量

    state,          // 属性,pushState和replaceState时传入的对象

    back,           // 方法,后退

    forward,        // 方法,前进

    go,             // 方法,前进或后退n个记录

    pushState,      // 方法,导航到新的路由,并记录在history中

    replaceState    // 方法,替换掉当前记录在history中的路由信息

}

 

// 订阅history变更事件

window.onpopstate = function (event) {

    ...

}

登入後複製

從對比中可以看出,兩者的關係是非常密切的,history函式庫可以說是history物件的超集,是功能更強大的history物件。

createHashHistory原始碼分析

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

// 构造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);

    };

    ...

}

登入後複製

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

下面再看下transitionManager做了什麼,重點看發布/訂閱相關內容,忽略用戶確認跳轉相關內容。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

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));

    };

    ...

}

登入後複製

這裡的程式碼一目了然,就是維護一個訂閱者列表,當hash變更的時候通知到相關的函數。

以上是hash改變的時候被動更新相關的內容,下面再看下主動更新相關的程式碼,以push為例,replace大同小異。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

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);

登入後複製

在瀏覽器進行前進後退操作時,history庫實際上是透過操作HTML5 history物件實現的。

1

2

3

4

5

6

7

8

9

10

const globalHistory = window.history;

 

const go = n => {

    ...

    globalHistory.go(n);

};

 

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

 

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

登入後複製

當呼叫window.history.go的時候,hash會發生變化,進而觸發hashchange事件,然後history庫再將變更通知到相關的訂閱者。

總結

本文對React Router核心依賴history函式庫進行了比較深入的介紹。從HTML5新增的history物件講起,比較了它跟history庫千絲萬縷的關係,並以hashHistory為例子詳細分析了其程式碼的實作細節。

最後,我們再來回顧history函式庫做了哪些事情:

  1. #借用HTML5 history物件的概念,在其基礎上又擴充了一些功能

  2. 提供3種類型的history:browserHistory,hashHistory,memoryHistory,並保持統一的api

  3. 支援發布/訂閱功能,當history發生改變的時候,可以自動觸發訂閱的函數

  4. 提供跳轉攔截、跳轉確認和basename等實用功能

雖然history庫是React Router的核心依賴,但它跟React本身並沒有依賴關係。如果你的專案中有操作history的場景,也可以將其引入專案中來。

相關建議:

使用h5實作react拖曳排序元件的方法(附程式碼)

HTML5如何解決margin-top的塌陷問題(附程式碼)

HTML5中標籤和常用規則有哪些? html5標籤以及規則的介紹


#

以上是React Router中的核心history函式庫的詳細分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板