JavaScript 状態コンテナ Redux の詳細な説明

JavaScript 状態コンテナ Redux の詳細な説明

1. Reduxを選ぶ理由

Redux を使用する理由について説明する前に、コンポーネントが通信する方法について説明しましょう。一般的なコンポーネント通信方法は次のとおりです。

  • 親子コンポーネント: 通信するためのプロパティ、状態/コールバック
  • シングルページアプリケーション: ルーティング値
  • EventEmitterリスナーコールバック値転送などのグローバルイベント
  • Reactにおけるクロスレベルコンポーネントデータ転送のコンテキスト(コンテキスト)

小規模でそれほど複雑でないアプリケーションでは、上記のコンポーネント通信方法で通常は十分です。

しかし、アプリケーションが複雑になるにつれて、データの状態(サーバー応答データ、ブラウザのキャッシュデータ、UI ステータス値など)が多すぎて、状態が頻繁に変化する可能性があります。上記のコンポーネント通信方法を使用すると、複雑で面倒になり、関連する問題の特定とデバッグが困難になります。

そのため、状態管理フレームワーク(Vuex、MobX、Redux など)が非常に必要であり、その中でも Redux は最も広く使用され、最も完成度の高いフレームワークです。

2. Reduxデータフロー

Redux を使用するアプリでは、次の 4 つの手順が実行されます。

ステップ 1: store.dispatch(action) を通じてアクションをトリガーします。ここで、action は、何が起こるかを説明するオブジェクトです。次のように:

{ タイプ: 'LIKE_ARTICLE'、記事 ID: 42 }
{ タイプ: 'FETCH_USER_SUCCESS'、レスポンス: { ID: 3、名前: 'Mary' } }
{ タイプ: 'ADD_TODO'、テキスト: '財務フロントエンド。' }

ステップ 2: Redux は指定した Reducer 関数を呼び出します。

ステップ 3: ルート Reducer は、複数の異なる Reducer 関数を 1 つの状態ツリーにマージします。

ステップ 4: Redux ストアは、ルート Reducer 関数から返された完全な状態ツリーを保存します。

諺にあるように、一枚の写真は千の言葉に値します。Redux データフロー図を使用して、このプロセスを理解しましょう。

3つの原則

1. 唯一の真実のソース: 単一のデータ ソース。アプリケーション全体の状態はオブジェクト ツリーに保存され、1 つのストアにのみ存在します。

2. 状態は読み取り専用です: 状態内の状態は読み取り専用です。状態を直接変更することはできません。アクションをトリガーすることによってのみ、新しい状態を返すことができます。

3. 変更は純粋関数で行われます。状態を変更するには純粋関数を使用します。

4. Reduxソースコード分析

Redux ソースコードには現在、js バージョンと ts バージョンがあります。この記事では、まず Redux ソースコードの js バージョンを紹介します。 Redux のソースコードの行数は多くないので、ソースコードの読解力を向上させたい開発者にとっては、早い段階で学習する価値があります。

Redux ソース コードは、主に 6 つのコア js ファイルと 3 つのツール js ファイルに分かれています。コア js ファイルは、index.js、createStore.js、compose.js、combineRuducers.js、bindActionCreators.js、applyMiddleware.js ファイルです。

次に、一つずつ勉強してみましょう。

4.1、インデックス

index.js はエントリ ファイルであり、createStore、combineReducers、applyMiddleware などのコア API を提供します。

輸出 {
  ストアを作成、
  結合リデューサー、
  バインドアクション作成者、
  ミドルウェアを適用、
  作曲する、
  __DO_NOT_USE__アクションタイプ
}

4.2、ストアの作成

createStore は、一意のストアを生成するために Redux によって提供される API です。ストアは、getState、dispatch、subscibe などのメソッドを提供します。Redux のストアは、アクションをディスパッチし、アクションを通じて対応する Reducer 関数を見つけて変更を加えることしかできません。

デフォルトの関数createStore(reducer, preloadedState, enhancer)をエクスポートします。
...
}

ソース コードから、createStore が Reducer、preloadedState、enhancer の 3 つのパラメーターを受け取ることがわかります。

Reducer は、ストア内の状態を変更できるアクションに対応する純粋な関数です。

preloadedState は、前の状態の初期化状態を表します。

enhancer は、applyMiddleware を通じてミドルウェアによって生成される拡張機能です。ストア内の getState メソッドは、現在のアプリケーションのストア内の状態ツリーを取得するために使用されます。

/**
 * ストアによって管理されている状態ツリーを読み取ります。
 *
 * @returns {any} アプリケーションの現在の状態ツリー。
 */
関数 getState() {
  if (isDispatching) {
    新しいエラーをスローします(
      'リデューサーの実行中に store.getState() を呼び出すことはできません。' +
        「リデューサーはすでに状態を引数として受け取っています。」 +
        「ストアから読み取るのではなく、トップ リデューサーから下に渡します。」
    )
  }
  現在の状態を返す
}

ディスパッチ メソッドはアクションをディスパッチするために使用され、状態の変更をトリガーできる唯一のメソッドです。 subscribe は、アクションがディスパッチされたとき、または状態が変更されたときに呼び出されるリスナーです。

4.3. 結合Reducers.js

/**
 * 異なるリデューサー関数の値を持つオブジェクトを1つの
 * リデューサー関数。すべての子リデューサーを呼び出し、その結果を収集します。
 * 渡されたキーに対応するキーを持つ単一の状態オブジェクトに変換されます。
 * リデューサー関数。
 */
デフォルトの関数をエクスポートする combineReducers(reducers) {
  const リデューサーキー = Object.keys(リデューサー)
     ...
  関数の組み合わせを返す(状態 = {}, アクション) {
     ...
    hasChanged = false とします
    定数 nextState = {}
    (i = 0 とします; i < finalReducerKeys.length; i++) {
      定数キー = finalReducerKeys[i]
      const リデューサー = finalReducers[キー]
      const previousStateForKey = 状態[キー]
      const nextStateForKey = リデューサー(前のStateForKey、アクション)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(キー、アクション)
        新しいエラーをスローします(errorMessage)
      }
      nextState[キー] = nextStateForKey
      //状態が変更されたかどうかを判断します hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    //変更が発生したかどうかに基づいて、新しい状態を返すか古い状態を返すかを決定します
    hasChanged を返します? nextState: 状態
  }
}

ソース コードから、入力パラメーターは Reducers であり、関数を返すことがわかります。 combineReducers は、すべての Reducer を 1 つの大きな Reducer 関数に結合します。重要な点は、Reducer が新しい状態を返すたびに、それを古い状態と比較することです。変更があった場合、hasChanged は true になり、ページの更新がトリガーされます。それ以外の場合、処理は行われません。

4.4、bindActionCreators.js

/**
 * アクションクリエーターの値を持つオブジェクトを、
 * 同じキーですが、すべての関数が`dispatch`呼び出しにラップされているため、
 * 直接呼び出すこともできます。これは単なる便利なメソッドであり、
 * `store.dispatch(MyActionCreators.doSomething())` を自分で実行してください。
 */
関数bindActionCreator(actionCreator, ディスパッチ) {
  関数()を返す{
    ディスパッチを返します(actionCreator.apply(this, arguments))
  }
}
 
デフォルト関数bindActionCreators(actionCreators, dispatch)をエクスポートします。
  if (typeof actionCreators === 'function') {
    bindActionCreator(actionCreators, dispatch) を返します。
  }
    ...
    ...
  定数キー = Object.keys(actionCreators)
  定数バウンドアクションクリエーター = {}
  (i = 0 とします; i < keys.length; i++) {
    定数キー = キー[i]
    const actionCreator = actionCreators[キー]
    if (typeof actionCreator === 'function') {
      boundActionCreators[キー] = bindActionCreator(actionCreator, ディスパッチ)
    }
  }
  boundActionCreators を返す
}

bindActionCreator は、単一の actionCreator をディスパッチにバインドし、bindActionCreators は、複数の actionCreator をディスパッチにバインドします。

bindActionCreator はアクションを送信するプロセスを簡素化します。返された関数が呼び出されると、対応するアクションを送信するために dispatch が自動的に呼び出されます。

bindActionCreators は、actionCreators の種類に応じて異なる処理を実行します。actionCreators が関数の場合は関数を返し、オブジェクトの場合はオブジェクトを返します。主な目的は、アクションをディスパッチ (アクション) 形式に変換して、アクションの分離を容易にし、コードをより簡潔にすることです。

4.5、compose.js

/**
 * 単一引数関数を右から左に構成します。右端の
 * 関数は複数の引数を取ることができるため、
 * 結果として得られる複合関数。
 *
 * @param {...Function} funcs 構成する関数。
 * @returns {Function} 引数の関数を組み合わせて得られる関数
 * 右から左へ。例えば、compose(f, g, h)は、
 * (...args) => f(g(h(...args)))。
 */
 
デフォルト関数をエクスポートする compose(...funcs) {
  関数の長さが0の場合{
    戻り値 arg => arg
  }
 
  関数の長さが1の場合{
    戻り関数[0]
  }
 
  funcs.reduce((a, b) => (...args) => a(b(...args))) を返します。
}

Compose は関数型プログラミングにおいて非常に重要な概念です。Compose を紹介する前に、まず Reduce とは何かを理解しましょう。公式ドキュメントでは、reduce を次のように定義しています:reduce() メソッドは、アキュムレータと配列内の各要素 (左から右へ) に関数を適用して、値に簡略化します。 Compose は、Reduce を利用して実装されるカリー化された関数です。複数の関数を 1 つの関数に結合して返します。主にミドルウェアで使用されます。

4.6、ミドルウェア.jsを適用する

/**
 * ディスパッチメソッドにミドルウェアを適用するストアエンハンサーを作成します
 * Reduxストアの。これは、例えば
 * 非同期アクションを簡潔に実行したり、すべてのアクション ペイロードをログに記録したりします。
 */
デフォルト関数 applyMiddleware(...middlewares) をエクスポートします。
  createStore を返します => (...args) => {
    const ストア = createStore(...引数)
    ...
    ...
    戻る {
      ...店、
      急送
    }
  }
}

applyMiddleware.js ファイルは、ミドルウェアの重要な API を提供します。ミドルウェアは主に store.dispatch を書き換えて、ディスパッチ機能を改善および拡張するために使用されます。

では、なぜミドルウェアが必要なのでしょうか?

まず、Reducer から始めなければなりません。Redux の 3 つの原則では、reducer は純粋関数でなければならないと述べられています。純粋関数の定義は次のとおりです。

  • 同じパラメータに対して同じ結果を返す
  • 結果は渡されたパラメータによって完全に異なります。
  • 副作用なし

リデューサーが純粋関数でなければならない理由については、次の点から始めることができます。

  • Redux は予測可能な状態マネージャーであるため、純粋な関数を使用すると Redux のデバッグが容易になり、問題を簡単に追跡して特定できるようになり、開発効率が向上します。
  • Redux は、新しいオブジェクトと古いオブジェクトのアドレスのみを比較し、それらが同じであるかどうかを確認します。つまり、浅い比較を通じて比較します。 Reducer 内で古い状態のプロパティ値を直接変更すると、古いオブジェクトと新しいオブジェクトの両方が同じオブジェクトを指します。浅い比較を引き続き使用すると、Redux は変更が発生していないと判断します。しかし、これを詳細な比較を通じて行うと、パフォーマンスが非常に低下します。最善の方法は、Redux が新しいオブジェクトを返し、新しいオブジェクトと古いオブジェクトを浅く比較することです。これは、Reducer が純粋な関数である重要な理由でもあります。

Reducer は純粋な関数ですが、アプリケーションでは、ログ記録/例外、非同期処理などの操作を処理する必要があります。これらの問題をどのように解決すればよいでしょうか?

この問題の答えはミドルウェアです。ディスパッチ機能はミドルウェアを通じて強化できます。以下は例です(ログ記録と例外記録)。

const store = createStore(リデューサー);
const next = store.dispatch;
 
// store.dispatch をオーバーライドする
store.dispatch = (アクション) => {
    試す {
        console.log('アクション:', アクション);
        console.log('現在の状態:', store.getState());
        次へ(アクション);
        console.log('次の状態', store.getState());
    } キャッチ(エラー){
        console.error('メッセージ:', エラー);
    }
}

5. シンプルなReduxをゼロから実装する

Redux (シンプルなカウンター) を一から実装するので、先ほど説明した store、Reducer、dispatch などの概念は忘れてください。Redux は状態マネージャーであるということだけを覚えておいてください。

まず、次のコードを見てみましょう。

状態 = {
    カウント: 1
}
//変更前 console.log (state.count);
//countの値を2に変更します
状態数 = 2;
//変更後 console.log (state.count);

変更前と変更後のカウント値を出力できる count フィールドを持つ状態オブジェクトを定義します。しかし、この時点で問題が見つかるでしょうか?つまり、参照カウントの他の場所ではカウントが変更されたことがわからないため、サブスクリプション パブリッシュ モデルを通じてこれを監視し、参照カウントの他の場所に通知する必要があります。したがって、次のようにコードをさらに最適化します。

状態 = {
    カウント: 1
};
//サブスクライブ関数subscribe(リスナー){
    リスナーをプッシュします。
}
関数 changeState(count) {
    状態.count = カウント;
    (i = 0 とします; i < listeners.length; i++) {
        定数リスナー = listeners[i];
        listener(); // 聞く}
}

この時点で、カウントを変更すると、すべてのリスナーに通知され、対応するアクションを実行できるようになります。しかし、現在、他に問題があるのでしょうか?たとえば、現在、状態には count フィールドのみが含まれています。フィールドが複数ある場合、処理方法は一貫していますか?同時に、公開コードをさらにカプセル化する必要があることも考慮する必要があります。次に、さらに最適化します。

const createStore = 関数 (initState) {
    状態を initState にします。
    //サブスクライブ関数subscribe(リスナー){
        リスナーをプッシュします。
    }
    関数 changeState (count) {
        状態.count = カウント;
        (i = 0 とします; i < listeners.length; i++) {
            定数リスナー = listeners[i];
            listener(); //通知}
    }
    関数 getState() {
        状態を返します。
    }
    戻る {
        購読する、
        状態の変更、
        状態を取得する
    }
}

コードから、最終的に 3 つの API が提供されることが分かります。これは、以前の Redux ソース コードのコア エントリ ファイル index.js に似ています。しかし、Redux はまだ実装されていません。状態に複数のフィールドを追加することをサポートし、Redux カウンターを実装する必要があります。

initState = {
    カウンター: {
        カウント: 0
    },
    情報:
        名前: ''、
        説明: ''
    }
}
store = createStore(initState); を作成します。
//出力カウント
ストア.subscribe(()=>{
    state = store.getState(); とします。
    console.log(状態.counter.count);
});
// 出力情報
ストア.subscribe(()=>{
    state = store.getState(); とします。
    console.log(`${state.info.name}:${state.info.description}`);
});

テストを通じて、現在、複数の属性フィールドを状態に格納できることが分かりました。次に、以前の changeState を変更して、自己増分と自己減分をサポートできるようにします。

// 自己増分 store.changeState({
    カウント: store.getState().count + 1
});
//自己デクリメント store.changeState({
    カウント: store.getState().count - 1
});
// 何でも変更 store.changeState({
    カウント: ファイナンス});

changeState を通じて自由に増加、減少、または変更できることがわかりましたが、これは必要なことではありません。カウンターを実装するときには、加算と減算の演算のみを実行できるようにする必要があるため、count の変更を制限する必要があります。したがって、changeState を制約し、タイプに基づいて異なる処理を実行するためのプラン メソッドを決定します。

関数プラン(状態、アクション) => {
  スイッチ(アクションタイプ){
    ケース 'INCREMENT':
      戻る {
        ...州、
        カウント: 状態.count + 1
      }
    ケース 'DECREMENT':
      戻る {
        ...州、
        カウント: 状態.count - 1
      }
    デフォルト:
      状態を返す
  }
}
store = createStore(plan, initState); を作成します。
// 自己増分 store.changeState({
    タイプ: 'INCREMENT'
});
//自己デクリメント store.changeState({
    タイプ: 'DECREMENT'
});

コード内で異なるタイプを異なる方法で処理しました。これで、状態内のカウントを自由に変更できなくなりました。changeState を正常に制約できました。 createStore の入力パラメータとして plan メソッドを使用し、状態を変更するときに plan メソッドに従って実行します。おめでとうございます!Redux を使用して簡単なカウンターを実装しました。

これはReduxですか?なぜこれがソースコードと違うのでしょうか?

次に、plan を Reducer に、changeState を dispatch に変更すると、これが Redux ソース コードによって実装された基本機能であることがわかります。これで、Redux データ フロー ダイアグラムを振り返ると、より明確になります。

6. Redux 開発ツール

Redux devtools は Redux のデバッグツールです。Chrome に対応するプラグインをインストールできます。 Redux に接続されたアプリケーションの場合、Redux devtools を使用すると、各リクエスト後に発生する変更を簡単に確認できるため、開発者は各操作の原因と結果を理解でき、開発とデバッグの効率が大幅に向上します。

上図に示すように、これは Redux devtools のビジュアル インターフェースです。左側の操作インターフェースは、現在のページ レンダリング プロセス中に実行されるアクションであり、右側の操作インターフェースは State に保存されているデータです。State からアクション パネルに切り替えると、アクションに対応する Reducer パラメータが表示されます。差分パネルに切り替えて、2 つの操作間で変更されたプロパティ値を表示します。

VII. 結論

Redux は、簡潔なソース コードと成熟したコミュニティ エコシステムを備えた優れた状態マネージャーです。たとえば、よく使われる react-redux と dva はどちらも Redux をカプセル化したものであり、現在大規模なアプリケーションで広く使用されています。ソースコードの読解力を向上させるために、Redux の公式 Web サイトとソースコードを通じて Redux のコアとなる考え方を学ぶことをお勧めします。

以上がJavaScript状態コンテナReduxの詳しい説明です。JavaScript状態コンテナReduxの詳細については、123WORDPRESS.COMの他の関連記事にも注目してください。

以下もご興味があるかもしれません:
  • react-redux における connect の使い方と原理分析の詳細な説明
  • 1 つの記事で React における Redux の初期の使用を理解する
  • reduxの動作原理と使い方の説明

<<:  Ubuntu 16.04/18.04 に Pycharm と Ipython をインストールするチュートリアル

>>:  mysql での rpm インストールの詳細な説明

推薦する

MySql 認証に基づく vsftpd 仮想ユーザー

目次1. MySQLのインストール1.2 テーブル、データベース、ユーザーを作成する1.3 リモート...

Docker Composeのデプロイと基本的な使い方の詳しい説明

1. Docker Composeの概要Compose は、マルチコンテナ Docker アプリケー...

ダイナミッククロックを実現するJS+CSS

この記事の例では、動的な時計を実装するためのJS + CSSの具体的なコードを参考までに共有していま...

JSベースの手持ち連射機能+テキスト揺れ特殊効果コードの簡単実装

少し前にTikTokで揺れる連打が流行っていたので真似してみることにしました。さっそく効果をみてみま...

Nodejs でタイムドクローラーを実装する完全な例

目次事件の原因Node Scheduleを使用してスケジュールされたタスクを実装する1. node-...

MySQLでBLOBデータを処理する方法

具体的なコードは次のとおりです。 パッケージ epoint.mppdb_01.h3c; java.i...

スクロールバーを非表示にしてコンテンツをスクロールする CSS サンプルコード

序文ページの HTML 構造にネストされたボックスが多数含まれている場合、ページに複数の垂直スクロー...

デジタル時計効果を実現するJavaScript

この記事の例では、JavaScriptでデジタル時計効果を実装するための具体的なコードを参考までに共...

Django2.* + Mysql5.7 開発環境統合チュートリアル図

環境: 10.12 の新機能Python 3.6 MySQL 5.7.25 の場合ジャンゴ 2.2....

MySQL の中国語ソートの詳細と例

MySQL の漢字ソートの詳細な説明デフォルトでは、MySQL は日付、時刻、および英語の文字列の並...

フロントエンドページのスライド検証を実装するための JavaScript + HTML (2)

この記事の例では、クールなフロントエンドページのスライド検証の具体的なコードを参考までに共有していま...

VueにExcelテーブルプラグインを導入する方法

この記事では、Excelテーブルプラグインを導入するVueの具体的なコードを参考までに共有します。具...

Tomcat の一般的な例外と解決コードの例

弊社のプロジェクトは Java で開発され、ミドルウェアは Tomcat でした。運用中に、Tomc...

K3s 入門ガイド - Docker で K3s を実行するための詳細なチュートリアル

k3dとは何ですか? k3d は、Docker で K3s クラスターを実行するための小さなプログ...