React Router のコア履歴ライブラリの詳細な分析

不言
リリース: 2018-08-14 11:01:56
オリジナル
3116 人が閲覧しました

この記事では React Router のコア履歴ライブラリの詳細な分析を紹介します。必要な方は参考にしていただければ幸いです。

前書き

ルーティング管理には、React Router がほぼ唯一の選択肢です。 React Router は 4 回のメジャー バージョン アップデートを経て、その機能はますます豊富になってきましたが、どのように変化しても、その中心となる履歴ライブラリへの依存は変わりません。 github に 4,000 以上のスターがあるこのライブラリがどのような機能を提供するかを見てみましょう。

HTML5 履歴オブジェクト

履歴ライブラリについて話すとき、この言葉に少し馴染みがあると思いませんか?はい、同じ名前の新しい履歴オブジェクトも HTML5 仕様に追加されました。この履歴オブジェクトがどのような問題を解決するために使用されるかを見てみましょう。

jQuery がフロントエンドを支配していた時代、ajax リクエストを介して更新せずにページを更新することは、当時非常に人気のあるページ処理方法であり、その時点で SPA のプロトタイプが進化しました。更新後も正しいページ要素が表示できるようにページへの変更をマークするには、通常、URL のハッシュ値が変更されてページを一意に特定できます。しかし、これは別の問題を引き起こします。ユーザーはページを切り替えるために「進む」/「戻る」を使用することができません。

この問題を解決するために、history オブジェクトが登場しました。ページの URL またはハッシュが変更されると、ブラウザは新しい URL を履歴オブジェクトに自動的にプッシュします。状態配列は、URL の変更を記録するために履歴オブジェクト内に維持されます。ブラウザが進む/戻る操作を実行すると、実際には履歴オブジェクトの対応するメソッド (forward/back) を呼び出し、対応する状態を取り出し、ページを切り替えます。 。 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库的:

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对象的:

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变更的。

// 构造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做了什么,重点看发布/订阅相关内容,忽略用户确认跳转相关内容。

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大同小异。

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对象实现的。

const globalHistory = window.history;

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

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

const goForward = () => go(1);
ログイン後にコピー

当调用window.history.go

URL の操作に加えて、履歴オブジェクトには、URL を操作せずに内部状態を更新するための 2 つのメソッド、つまり pushStatereplaceState も提供されます。追加データを状態に保存し、onpopstate イベントの event.state を通じて取得することもできます。履歴オブジェクトをより深く理解したい場合は、こことここを参照してください。

履歴ライブラリと HTML5 履歴オブジェクトの関係

履歴ライブラリに戻って見てみましょう。基本的に次の 4 つのことを行います:

  1. HTML5 履歴オブジェクトの概念を利用し、それに基づいていくつかの機能を拡張します

  2. 3 種類の履歴を提供します:browserHistory、hashHistory、memoryHistory を提供し、統一性を維持します API

  3. はパブリッシュ/サブスクライブ機能をサポートしており、履歴が変更されると、サブスクリプション機能が自動的にトリガーされます

  4. ジャンプインターセプト、ジャンプ確認、ベースネームなどの実用的な機能を提供します

2つの類似点のいくつかを比較してみましょう。 API の違いについても説明します。以下は履歴ライブラリです:

rrreee

以下は HTML5 履歴オブジェクトです: 🎜rrreee🎜 比較すると、この 2 つの関係は非常に密接であることがわかります。履歴ライブラリは、履歴オブジェクトよりも強力です。 🎜🎜createHashHistory ソース コード分析🎜🎜 以下では、3 つの履歴タイプの 1 つである hashHistory を例として履歴ソース コードを分析し、その動作を確認します。まず、ハッシュの変更がどのように処理されるかを見てみましょう。 🎜rrreee🎜 上記は、パッシブなハッシュ変更を処理するためのロジックを 1 つの文で要約すると、次のように要約できます: ハッシュ変更イベントをサブスクライブし、変更が本当に必要かどうかを判断し、変更が必要な場合は独自の属性を更新します。変更が必要ない場合は、前の状態に戻ります。 🎜🎜transitionManager の動作を見てみましょう。ユーザー確認ジャンプ関連のコンテンツは無視して、発行/サブスクリプション関連のコンテンツに注目してください。 🎜rrreee🎜 ここのコードは、サブスクライバーのリストを管理し、ハッシュが変更されたときに関連する関数に通知することが一目瞭然です。 🎜🎜上記はハッシュ変更時のパッシブ更新に関連する内容です。push を例として、replace に関連するコードを見てみましょう。似ている。 🎜rrreee🎜ブラウザが前進および後退操作を実行するとき、履歴ライブラリは実際には HTML5 履歴オブジェクトを操作することによって実装されます。 🎜rrreee🎜 window.history.go が呼び出されると、ハッシュが変更され、hashchange イベントがトリガーされ、履歴ライブラリが関連するサブスクライバーに変更を通知します。 🎜🎜概要🎜🎜この記事では、React Router コアが依存する履歴ライブラリについてより詳しく紹介します。 HTML5 の新しい履歴オブジェクトから始めて、その密接な関係と履歴ライブラリを比較し、例として hashHistory を使用して、そのコードの実装詳細を詳細に分析します。 🎜🎜最後に、履歴ライブラリが何をしたかを確認してみましょう: 🎜🎜🎜🎜 HTML5 履歴オブジェクトの概念から学び、それに基づいていくつかの機能を拡張します 🎜🎜🎜🎜 3 種類の履歴を提供します:browserHistory、hashHistory、memoryHistory、統一された API を維持します🎜🎜🎜🎜 パブリッシュ/サブスクライブ機能をサポートし、履歴が変更されると、サブスクリプション機能を自動的にトリガーできます🎜🎜🎜🎜 ジャンプインターセプト、ジャンプ確認、ベース名などの実用的な機能を提供します🎜🎜🎜 🎜履歴ライブラリーですがは React Router の中心的な依存関係であり、React 自体には依存しません。プロジェクトに履歴操作シナリオがある場合は、それをプロジェクトに導入することもできます。 🎜

関連する推奨事項:

h5 を使用して React ドラッグ アンド ドロップの並べ替えコンポーネントを実装する方法 (コード付き)

HTML5 でマージン上部の折りたたみ問題を解決する方法 (コード付き)

タグと共通のものとは何ですかHTML5のルール? HTML5のタグとルールの紹介


以上がReact Router のコア履歴ライブラリの詳細な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート