Redux-quick-guide

提供:Dev Guides
移動先:案内検索

Redux-概要

Reduxは、JavaScriptアプリの予測可能な状態コンテナーです。 アプリケーションが大きくなると、アプリケーションを整理してデータフローを維持することが難しくなります。 Reduxは、ストアと呼ばれる単一のグローバルオブジェクトでアプリケーションの状態を管理することにより、この問題を解決します。 Reduxの基本原則は、アプリケーション全体の一貫性を維持するのに役立ち、デバッグとテストが容易になります。

さらに重要なことは、タイムトラベルデバッガーと組み合わせたライブコード編集を提供します。 React、Angular、Vueなどのビューレイヤーに柔軟に対応できます。

Reduxの原則

Reduxの予測可能性は、以下に示す3つの最も重要な原則によって決まります-

単一の真実の源

アプリケーション全体の状態は、単一のストア内のオブジェクトツリーに保存されます。 アプリケーションの状態全体が単一のツリーに保存されるため、デバッグが容易になり、開発が高速になります。

状態は読み取り専用です

状態を変更する唯一の方法は、何が起こったかを説明するオブジェクトであるアクションを発行することです。 これは、誰もアプリケーションの状態を直接変更できないことを意味します。

変更は純粋な関数で行われます

アクションによる状態ツリーの変換方法を指定するには、純粋なレデューサーを作成します。 レデューサーは、状態の変更が行われる中心的な場所です。 Reducerは、状態とアクションを引数として取り、新しく更新された状態を返す関数です。

Redux-インストール

Reduxをインストールする前に、 NodejsとNPM をインストールする必要があります。 以下は、インストールに役立つ手順です。 既にNodejsとNPMがデバイスにインストールされている場合は、これらの手順をスキップできます。

  • [[1]]
  • インストーラーを実行し、指示に従ってライセンス契約に同意します。
  • デバイスを再起動して実行します。
  • コマンドプロンプトを開き、node -vと入力して、インストールが成功したことを確認できます。 これにより、システム内のノードの最新バージョンが表示されます。
  • npmが正常にインストールされたかどうかを確認するには、npm –vと入力して最新のnpmバージョンを返します。

reduxをインストールするには、次の手順に従ってください-

コマンドプロンプトで次のコマンドを実行して、Reduxをインストールします。

npm install --save redux

反応アプリケーションでReduxを使用するには、次のように追加の依存関係をインストールする必要があります-

npm install --save react-redux

Reduxの開発者ツールをインストールするには、依存関係として次をインストールする必要があります-

コマンドプロンプトで以下のコマンドを実行して、Redux dev-toolsをインストールします。

npm install --save-dev redux-devtools

Redux開発ツールをインストールしてプロジェクトに統合したくない場合は、ChromeおよびFirefox用の Redux DevTools Extension をインストールできます。

Redux-コアコンセプト

私たちのアプリケーションの状態は、次のような initialState と呼ばれるプレーンなオブジェクトによって記述されていると仮定しましょう-

const initialState = {
   isLoading: false,
   items: [],
   hasError: false
};

アプリケーションのすべてのコードでこの状態を変更することはできません。 状態を変更するには、アクションをディスパッチする必要があります。

アクションとは何ですか?

アクションは、タイププロパティで変更を引き起こす意図を記述する単純なオブジェクトです。 どのタイプのアクションが実行されているかを伝えるtypeプロパティが必要です。 アクションのコマンドは次のとおりです-

return {
   type: 'ITEMS_REQUEST',//action type
   isLoading: true//payload information
}

アクションと状態は、Reducerと呼ばれる関数によって保持されます。 アクションは、変更を引き起こす目的でディスパッチされます。 この変更は、リデューサーによって実行されます。 レデューサーは、Reduxで状態を変更する唯一の方法であり、より予測可能で、集中化され、デバッグ可能になります。 「ITEMS_REQUEST」アクションを処理するレデューサー関数は次のとおりです-

const reducer = (state = initialState, action) => {//es6 arrow function
   switch (action.type) {
      case 'ITEMS_REQUEST':
         return Object.assign({}, state, {
            isLoading: action.isLoading
         })
      default:
         return state;
   }
}

Reduxには、アプリケーションの状態を保持する単一のストアがあります。 データ処理ロジックに基づいてコードを分割する場合は、Reduxのストアの代わりにレデューサーの分割を開始する必要があります。

このチュートリアルの後半で、レデューサーを分割してストアと組み合わせる方法について説明します。

Reduxコンポーネントは次のとおりです-

データ処理ロジック

Redux-データフロー

Reduxは単方向のデータフローに従います。 これは、アプリケーションデータが一方向のバインディングデータフローに従うことを意味します。 アプリケーションが大きくなり複雑になると、アプリケーションの状態を制御できない場合、問題を再現して新しい機能を追加するのは困難です。

Reduxは、状態の更新がいつどのように発生するかを制限することにより、コードの複雑さを軽減します。 このようにして、更新された状態の管理は簡単です。 Reduxの3つの原則としての制限についてはすでに知っています。 次の図は、Reduxデータフローをよりよく理解するのに役立ちます-

データフロー

  • ユーザーがアプリケーションを操作すると、アクションがディスパッチされます。
  • ルートレデューサー関数は、現在の状態とディスパッチされたアクションで呼び出されます。 ルートレデューサーは、タスクをより小さなレデューサー関数に分割し、最終的に新しい状態を返します。
  • ストアは、コールバック関数を実行してビューに通知します。
  • ビューは更新された状態を取得し、再レンダリングできます。

Redux-ストア

ストアは、Reduxの不変オブジェクトツリーです。 ストアは、アプリケーションの状態を保持する状態コンテナーです。 Reduxは、アプリケーション内に1つのストアのみを持つことができます。 Reduxでストアが作成されるたびに、リデューサーを指定する必要があります。

Reduxの createStore メソッドを使用してストアを作成する方法を見てみましょう。 以下に示すように、ストア作成プロセスをサポートするReduxライブラリからcreateStoreパッケージをインポートする必要があります-

import { createStore } from 'redux';
import reducer from './reducers/reducer'
const store = createStore(reducer);

createStore関数には3つの引数を指定できます。 以下は構文です-

createStore(reducer, [preloadedState], [enhancer])

レデューサーは、アプリの次の状態を返す関数です。 preloadedStateはオプションの引数であり、アプリの初期状態です。 エンハンサーもオプションの引数です。 サードパーティの機能を使用してストアを強化するのに役立ちます。

ストアには、以下の3つの重要な方法があります-

getState

Reduxストアの現在の状態を取得するのに役立ちます。

getStateの構文は次のとおりです-

store.getState()

ディスパッチ

アプリケーションの状態を変更するアクションをディスパッチできます。

ディスパッチの構文は次のとおりです-

store.dispatch({type:'ITEMS_REQUEST'})

申し込む

アクションがディスパッチされたときにReduxストアが呼び出すコールバックを登録するのに役立ちます。 Reduxの状態が更新されるとすぐに、ビューは自動的に再レン​​ダリングされます。

ディスパッチの構文は次のとおりです-

store.subscribe(()=>{ console.log(store.getState());})

サブスクライブ関数は、リスナーをサブスクライブ解除するための関数を返すことに注意してください。 リスナーの購読を解除するには、次のコードを使用できます-

const unsubscribe = store.subscribe(()=>{console.log(store.getState());});
unsubscribe();

Redux-アクション

Reduxの公式ドキュメントによると、アクションはストアの唯一の情報源です。 アプリケーションから保存する情報のペイロードを運びます。

前に説明したように、アクションは実行されるアクションのタイプを示すtype属性を持たなければならないプレーンなJavaScriptオブジェクトです。 何が起こったかを教えてくれます。 タイプは、以下に示すように、アプリケーションで文字列定数として定義する必要があります-

const ITEMS_REQUEST = 'ITEMS_REQUEST';

このタイプ属性とは別に、アクションオブジェクトの構造は開発者次第です。 アクションオブジェクトをできるだけ軽く保ち、必要な情報のみを渡すことをお勧めします。

ストアに変更を加えるには、最初にstore.dispatch()関数を使用してアクションをディスパッチする必要があります。 アクションオブジェクトは次のとおりです-

{ type: GET_ORDER_STATUS , payload: {orderId,userId } }
{ type: GET_WISHLIST_ITEMS, payload: userId }

アクションクリエーター

アクションクリエーターは、アクションオブジェクトの作成プロセスをカプセル化する関数です。 これらの関数は、アクションであるプレーンなJsオブジェクトを返すだけです。 クリーンなコードの記述を促進し、再利用性の実現に役立ちます。

サーバーからの商品アイテムリストデータを要求するアクション「* ITEMS_REQUEST」をディスパッチできるアクション作成者について学びましょう。 一方、「ITEMS_REQUEST」アクションタイプのリデューサーで *isLoading 状態がtrueになり、アイテムがロードされていることを示し、データはまだサーバーから受信されていません。

最初は、 initialState オブジェクトのisLoading状態はfalseで、何もロードされていないと仮定しました。 ブラウザでデータを受信すると、対応するリデューサーの「ITEMS_REQUEST_SUCCESS」アクションタイプでisLoading状態がfalseとして返されます。 この状態は、データの要求がオンになっている間、ページにローダー/メッセージを表示するために、Reactコンポーネントの支柱として使用できます。 アクションの作成者は次のとおりです-

const ITEMS_REQUEST = ‘ITEMS_REQUEST’ ;
const ITEMS_REQUEST_SUCCESS = ‘ITEMS_REQUEST_SUCCESS’ ;
export function itemsRequest(bool,startIndex,endIndex) {
   let payload = {
      isLoading: bool,
      startIndex,
      endIndex
   }
   return {
      type: ITEMS_REQUEST,
      payload
   }
}
export function itemsRequestSuccess(bool) {
   return {
      type: ITEMS_REQUEST_SUCCESS,
      isLoading: bool,
   }
}

ディスパッチ関数を呼び出すには、アクションを引数としてディスパッチ関数に渡す必要があります。

dispatch(itemsRequest(true,1, 20));
dispatch(itemsRequestSuccess(false));

store.dispatch()を直接使用して、アクションをディスパッチできます。 ただし、* connect()*と呼ばれるreact-Reduxヘルパーメソッドを使用してアクセスする可能性が高くなります。 * bindActionCreators()*メソッドを使用して、多くのアクションクリエーターをディスパッチ関数にバインドすることもできます。

Redux-純粋な機能

関数は、引数と呼ばれる入力を受け取り、戻り値と呼ばれる出力を生成するプロセスです。 関数は、次の規則に従っている場合、純粋と呼ばれます-

  • 関数は、同じ引数に対して同じ結果を返します。
  • その評価には副作用はありません。つまり、入力データを変更しません。
  • ローカル変数とグローバル変数の突然変異はありません。 *グローバル変数のような外部状態に依存しません。

関数への入力として渡された値の2回を返す関数の例を見てみましょう。 一般的に、f(x)⇒ x* 2と記述されます。 引数値2で関数が呼び出された場合、出力は4、f(2)⇒ 4になります。

以下に示すようにJavaScriptで関数の定義を書きましょう-

const double = x => x*2;//es6 arrow function
console.log(double(2)); //4

ここで、doubleは純関数です。

Reduxの3つの原則に従って、純粋な機能、つまりReduxのレデューサーによって変更を行う必要があります。 ここで、減速機が純粋な関数でなければならない理由について疑問が生じます。

タイプが 'ADD_TO_CART_SUCCESS' のアクションをディスパッチして、[カートに追加]ボタンをクリックしてアイテムをショッピングカートアプリケーションに追加するとします。

減速機が以下に示すようにカートにアイテムを追加していると仮定しましょう-

const initialState = {
   isAddedToCart: false;
}
const addToCartReducer = (state = initialState, action) => {//es6 arrow function
   switch (action.type) {
      case 'ADD_TO_CART_SUCCESS' :
         state.isAddedToCart = !state.isAddedToCart;//original object altered
         return state;
      default:
         return state;
   }
}
export default addToCartReducer ;
*isAddedToCart* は状態オブジェクトのプロパティであり、ブール値 *'trueまたはfalse'* を返すことにより、アイテムの「カートに追加」ボタンを無効にするタイミングを決定できます。 これにより、ユーザーは同じ製品を複数回追加できなくなります。 これで、新しいオブジェクトを返す代わりに、上記のような状態でisAddedToCartプロパティを変更しています。 ここで、アイテムをカートに追加しようとしても、何も起こりません。 [カートに追加]ボタンは無効になりません。

この動作の理由は次のとおりです-

Reduxは、両方のオブジェクトのメモリ位置によって古いオブジェクトと新しいオブジェクトを比較します。 変更が発生すると、reducerからの新しいオブジェクトが必要です。 また、変更が発生しない場合は、古いオブジェクトを取得することも想定しています。 この場合、同じです。 このため、Reduxは何も発生していないと想定しています。

そのため、レデューサーはReduxの純粋な関数である必要があります。 以下は、突然変異なしでそれを書く方法です-

const initialState = {
   isAddedToCart: false;
}
const addToCartReducer = (state = initialState, action) => {//es6 arrow function
   switch (action.type) {
      case 'ADD_TO_CART_SUCCESS' :
         return {
            ...state,
            isAddedToCart: !state.isAddedToCart
         }
      default:
         return state;
   }
}
export default addToCartReducer;

Redux-レデューサー

レデューサーはReduxの純粋な機能です。 純粋な機能は予測可能です。 レデューサーは、Reduxで状態を変更する唯一の方法です。 ロジックと計算を記述できる唯一の場所です。 Reducer関数は、アプリの以前の状態とディスパッチされるアクションを受け入れ、次の状態を計算して新しいオブジェクトを返します。

次のいくつかのことは、レデューサー内で実行しないでください-

  • 関数の引数の突然変異
  • API呼び出しとルーティングロジック
  • 非純粋関数の呼び出し Math.random()

以下は、レデューサーの構文です-

(state,action) => newState

アクションクリエーターモジュールで説明したWebページに製品アイテムのリストを表示する例を続けましょう。 その減速機を書く方法を以下に見てみましょう。

const initialState = {
   isLoading: false,
   items: []
};
const reducer = (state = initialState, action) => {
   switch (action.type) {
      case 'ITEMS_REQUEST':
         return Object.assign({}, state, {
            isLoading: action.payload.isLoading
         })
      case ‘ITEMS_REQUEST_SUCCESS':
         return Object.assign({}, state, {
            items: state.items.concat(action.items),
            isLoading: action.isLoading
         })
      default:
         return state;
   }
}
export default reducer;

まず、状態を「initialState」に設定しない場合、Reduxは未定義の状態でreducerを呼び出します。 このコード例では、JavaScriptのconcat()関数が「ITEMS_REQUEST_SUCCESS」で使用されていますが、既存の配列は変更されません。代わりに、新しい配列を返します。

この方法で、状態の突然変異を回避できます。 州に直接書かないでください。 「ITEMS_REQUEST」では、受信したアクションから状態値を設定する必要があります。

レデューサーでロジックを記述し、論理データに基づいて分割できることは既に説明しました。 大規模なアプリケーションを扱う場合、レデューサーを分割してルートレジューサーとして組み合わせる方法を見てみましょう。

ユーザーが製品の注文ステータスにアクセスしてウィッシュリスト情報を表示できるWebページを設計するとします。 異なるレデューサーファイルでロジックを分離し、それらを独立して動作させることができます。 GET_ORDER_STATUSアクションがディスパッチされ、ある注文IDとユーザーIDに対応する注文のステータスを取得すると仮定しましょう。

/reducer/orderStatusReducer.js
import { GET_ORDER_STATUS } from ‘../constants/appConstant’;
export default function (state = {} , action) {
   switch(action.type) {
      case GET_ORDER_STATUS:
         return { ...state, orderStatusData: action.payload.orderStatus };
      default:
         return state;
   }
}

同様に、GET_WISHLIST_ITEMSアクションがディスパッチされ、ユーザーのウィッシュリスト情報を取得すると仮定します。

/reducer/getWishlistDataReducer.js
import { GET_WISHLIST_ITEMS } from ‘../constants/appConstant’;
export default function (state = {}, action) {
   switch(action.type) {
      case GET_WISHLIST_ITEMS:
         return { ...state, wishlistData: action.payload.wishlistData };
      default:
         return state;
   }
}

ReduxのcombinedReducersユーティリティを使用して、両方のレデューサーを結合できるようになりました。 composeReducersは値が異なるリデューサー関数であるオブジェクトを返す関数を生成します。 インデックスリデューサーファイルにすべてのリデューサーをインポートし、それらをそれぞれの名前を持つオブジェクトとして結合できます。

/reducer/index.js
import { combineReducers } from ‘redux’;
import OrderStatusReducer from ‘./orderStatusReducer’;
import GetWishlistDataReducer from ‘./getWishlistDataReducer’;

const rootReducer = combineReducers ({
   orderStatusReducer: OrderStatusReducer,
   getWishlistDataReducer: GetWishlistDataReducer
});
export default rootReducer;

さて、次のようにこのrootReducerをcreateStoreメソッドに渡すことができます-

const store = createStore(rootReducer);

Redux-ミドルウェア

Redux自体は同期なので、ネットワークリクエスト*などの *async 操作はReduxでどのように機能しますか ここでは、ミドルウェアが便利です。 前述のように、リデューサーはすべての実行ロジックが記述される場所です。 Reducerは、誰がそれを実行するか、アクションがディスパッチされる前と後のアプリの状態を取得または記録する時間とは関係ありません。

この場合、Reduxミドルウェア機能は、リデューサーに到達する前にディスパッチされたアクションと対話するための媒体を提供します。 カスタマイズされたミドルウェア関数は、高次関数(別の関数を返す関数)を作成することで作成できます。これは、いくつかのロジックをラップします。 複数のミドルウェアを組み合わせて新しい機能を追加することができ、各ミドルウェアは前後の知識を必要としません。 ミドルウェアは、ディスパッチされたアクションとレデューサーの間のどこかに想像できます。

一般に、ミドルウェアはアプリの非同期アクションを処理するために使用されます。 ReduxはapplyMiddlewareと呼ばれるAPIを提供します。これにより、redux-thunkやredux-promiseなどのReduxミドルウェアだけでなく、カスタムミドルウェアも使用できます。 ミドルウェアをストアに適用します。 applyMiddleware APIを使用する構文は次のとおりです-

applyMiddleware(...middleware)

そして、これは次のようにストアに適用することができます-

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
const store = createStore(rootReducer, applyMiddleware(thunk));

ミドルウェア

ミドルウェアでは、アクションオブジェクトの代わりに関数を返すアクションディスパッチャを作成できます。 同じ例を以下に示します-

function getUser() {
   return function() {
      return axios.get('/get_user_details');
   };
}

条件付きディスパッチはミドルウェア内に記述できます。 各ミドルウェアは、新しいアクションをディスパッチできるようにストアのディスパッチを受け取り、引数としてgetState関数を受け取り、現在の状態にアクセスして関数を返すことができます。 内部関数からの戻り値は、ディスパッチ関数自体の値として使用できます。

以下はミドルウェアの構文です-

({ getState, dispatch }) => next => action

getState関数は、現在の状態に応じて、新しいデータをフェッチするか、キャッシュ結果を返すかを決定するのに役立ちます。

カスタムミドルウェアロガー関数の例を見てみましょう。 アクションと新しい状態を記録するだけです。

import { createStore, applyMiddleware } from 'redux'
import userLogin from './reducers'

function logger({ getState }) {
   return next => action => {
      console.log(‘action’, action);
      const returnVal = next(action);
      console.log('state when action is dispatched', getState());
      return returnVal;
   }
}

次のコード行を記述して、ロガーミドルウェアをストアに適用します-

const store = createStore(userLogin , initialState=[ ] , applyMiddleware(logger));

以下のコードを使用して、ディスパッチされたアクションと新しい状態をチェックするアクションをディスパッチします-

store.dispatch({
   type: 'ITEMS_REQUEST',
    isLoading: true
})

ローダーを表示または非表示にするタイミングを処理できるミドルウェアの別の例を以下に示します。 このミドルウェアは、リソースを要求するときにローダーを表示し、リソースの要求が完了するとローダーを非表示にします。

import isPromise from 'is-promise';

function loaderHandler({ dispatch }) {
   return next => action => {
      if (isPromise(action)) {
         dispatch({ type: 'SHOW_LOADER' });
         action
            .then(() => dispatch({ type: 'HIDE_LOADER' }))
            .catch(() => dispatch({ type: 'HIDE_LOADER' }));
      }
      return next(action);
   };
}
const store = createStore(
   userLogin , initialState = [ ] ,
   applyMiddleware(loaderHandler)
);

Redux-開発ツール

Redux-Devtoolsは、Reduxアプリのデバッグプラットフォームを提供します。 タイムトラベルのデバッグとライブ編集を実行できます。 公式ドキュメントの機能の一部は次のとおりです-

  • これにより、すべての状態とアクションのペイロードを検査できます。
  • アクションを「キャンセル」することで、時間を遡ることができます。
  • レデューサーコードを変更すると、各「ステージング」アクションが再評価されます。
  • レデューサーがスローする場合、エラーを特定できます。また、アクション中にこれが発生しました。
  • persistState()ストアエンハンサーを使用すると、ページのリロード後もデバッグセッションを維持できます。

以下に示すように、Redux dev-toolsには2つのバリアントがあります-

*Redux DevTools* -パッケージとしてインストールし、以下に示すようにアプリケーションに統合できます-

https://github.com/reduxjs/redux-devtools/blob/master/docs/Walkthrough.md#manual-integration

*Redux DevTools Extension* -Reduxと同じ開発者ツールを実装するブラウザー拡張機能は次のとおりです-

https://github.com/zalmoxisus/redux-devtools-extension

Redux devツールの助けを借りて、アクションをスキップして時間に戻る方法を確認しましょう。 次のスクリーンショットでは、アイテムのリストを取得するために以前にディスパッチしたアクションについて説明しています。 ここでは、インスペクタータブにディスパッチされたアクションを見ることができます。 右側には、状態ツリーの違いを示す[デモ]タブがあります。

インスペクタータブ

このツールを使い始めると、このツールに慣れるでしょう。 このReduxプラグインツールから実際のコードを記述することなく、アクションをディスパッチできます。 最後の行のDispatcherオプションは、これに役立ちます。 アイテムが正常にフェッチされる最後のアクションを確認しましょう。

正常に取得されました

サーバーからの応答としてオブジェクトの配列を受け取りました。 すべてのデータは、ページにリストを表示するために利用できます。 右上の状態タブをクリックして、ストアの状態を同時に追跡することもできます。

状態タブ

前のセクションでは、タイムトラベルのデバッグについて学習しました。 ここで、1つのアクションをスキップして、時間を遡ってアプリの状態を分析する方法を確認しましょう。 アクションタイプをクリックすると、「ジャンプ」と「スキップ」の2つのオプションが表示されます。

特定のアクションタイプでスキップボタンをクリックすると、特定のアクションをスキップできます。 アクションが起こらなかったかのように動作します。 特定のアクションタイプでジャンプボタンをクリックすると、そのアクションが発生したときの状態に移動し、残りのすべてのアクションを順番にスキップします。 これにより、特定のアクションが発生したときに状態を保持できます。 この機能は、アプリケーションのデバッグおよびエラーの検出に役立ちます。

ジャンプボタン

最後のアクションをスキップし、バックグラウンドからのすべてのリストデータが消えました。 アイテムのデータが到着しておらず、アプリにページにレンダリングするデータがない時間に戻ります。 実際には、コーディングが簡単になり、デバッグが容易になります。

Redux-テスト

Reduxコードのテストは、ほとんどの場合関数を作成するため簡単であり、それらのほとんどは純粋です。 そのため、モックすることなくテストできます。 ここでは、テストエンジンとしてJESTを使用しています。 ノード環境で機能し、DOMにアクセスしません。

以下に示すコードでJESTをインストールできます-

npm install --save-dev jest

バベルでは、次のように babel-jest をインストールする必要があります-

npm install --save-dev babel-jest

そして、次のように.babelrcファイルでbabel-preset-env機能を使用するように設定します-

{
   "presets": ["@babel/preset-env"]
}
And add the following script in your package.json:
{
  //Some other code
   "scripts": {
     //code
      "test": "jest",
      "test:watch": "npm test -- --watch"
   },
  //code
}

最後に、 npm testまたはnpm run test を実行します。 アクションクリエーターとリデューサーのテストケースを作成する方法を確認しましょう。

アクションクリエーター向けのテストケース

以下に示すように、アクション作成者がいると仮定しましょう-

export function itemsRequestSuccess(bool) {
   return {
      type: ITEMS_REQUEST_SUCCESS,
      isLoading: bool,
   }
}

このアクション作成者は、以下のようにテストできます-

import *as action from '../actions/actions';
import* as types from '../../constants/ActionTypes';

describe('actions', () => {
   it('should create an action to check if item is loading', () => {
      const isLoading = true,
      const expectedAction = {
         type: types.ITEMS_REQUEST_SUCCESS, isLoading
      }
      expect(actions.itemsRequestSuccess(isLoading)).toEqual(expectedAction)
   })
})

レデューサーのテストケース

アクションが適用されると、reducerは新しい状態を返す必要があることを学びました。 そのため、リデューサーはこの動作でテストされます。

以下に示すような減速機を検討してください-

const initialState = {
   isLoading: false
};
const reducer = (state = initialState, action) => {
   switch (action.type) {
      case 'ITEMS_REQUEST':
         return Object.assign({}, state, {
            isLoading: action.payload.isLoading
         })
      default:
         return state;
   }
}
export default reducer;

上記の減速機をテストするには、減速機に状態とアクションを渡し、以下に示すように新しい状態を返す必要があります-

import reducer from '../../reducer/reducer'
import * as types from '../../constants/ActionTypes'

describe('reducer initial state', () => {
   it('should return the initial state', () => {
      expect(reducer(undefined, {})).toEqual([
         {
            isLoading: false,
         }
      ])
   })
   it('should handle ITEMS_REQUEST', () => {
      expect(
         reducer(
            {
               isLoading: false,
            },
            {
               type: types.ITEMS_REQUEST,
               payload: { isLoading: true }
            }
         )
      ).toEqual({
         isLoading: true
      })
   })
})

テストケースの作成に慣れていない場合は、https://jestjs.io/[JEST]の基本を確認できます。

Redux-Reactの統合

前の章で、Reduxとは何か、どのように機能するかを学びました。 ここで、ビューパーツとReduxの統合を確認しましょう。 任意のビューレイヤーをReduxに追加できます。 また、reactライブラリとReduxについても説明します。

さまざまな反応コンポーネントが、最上位コンポーネントから最下位コンポーネントまですべてのコンポーネントに小道具として渡すことなく、同じデータを異なる方法で表示する必要がある場合を考えてみましょう。 反応コンポーネントの外部に保存することが理想的です。 なぜなら、データをさまざまなコンポーネントに完全に渡す必要がないため、データの取得が速くなるからです。

Reduxでそれがどのように可能かを議論しましょう。 Reduxはreact-reduxパッケージを提供し、以下に示す2つのユーティリティでreactコンポーネントをバインドします-

  • プロバイダ
  • 接続する

プロバイダーは、ストアをアプリケーションの残りの部分で利用できるようにします。 接続機能は、コンポーネントがストアに接続するように反応し、ストアの状態で発生する各変更に対応するのに役立ちます。

ストアを作成し、react-reduxアプリでアプリの残りの部分にストアを有効にするプロバイダーを使用する root index.js ファイルを見てみましょう。

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, applyMiddleware } from 'redux';
import reducer from './reducers/reducer'
import thunk from 'redux-thunk';
import App from './components/app'
import './index.css';

const store = createStore(
   reducer,
   window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__(),
   applyMiddleware(thunk)
)
render(
   <Provider store = {store}>
      <App/>
   </Provider>,
   document.getElementById('root')
)

react-reduxアプリで変更が発生するたびに、mapStateToProps()が呼び出されます。 この関数では、reactコンポーネントに提供する必要がある状態を正確に指定します。

以下で説明するconnect()関数の助けを借りて、これらのアプリの状態を反応コンポーネントに接続しています。 Connect()は、コンポーネントをパラメーターとしてとる高次関数です。 特定の操作を実行し、最終的にエクスポートした正しいデータを含む新しいコンポーネントを返します。

mapStateToProps()の助けを借りて、これらのストア状態を反応コンポーネントの支柱として提供します。 このコードは、コンテナコンポーネントにラップできます。 動機は、データの取得、レンダリングの懸念、再利用性などの懸念を分離することです。

import { connect } from 'react-redux'
import Listing from '../components/listing/Listing'//react component
import makeApiCall from '../services/services'//component to make api call

const mapStateToProps = (state) => {
   return {
      items: state.items,
      isLoading: state.isLoading
   };
};
const mapDispatchToProps = (dispatch) => {
   return {
      fetchData: () => dispatch(makeApiCall())
   };
};
export default connect(mapStateToProps, mapDispatchToProps)(Listing);

services.jsファイルでAPI呼び出しを行うコンポーネントの定義は次のとおりです-

import axios from 'axios'
import { itemsLoading, itemsFetchDataSuccess } from '../actions/actions'

export default function makeApiCall() {
   return (dispatch) => {
      dispatch(itemsLoading(true));
      axios.get('http://api.tvmaze.com/shows')
      .then((response) => {
         if (response.status !== 200) {
            throw Error(response.statusText);
         }
         dispatch(itemsLoading(false));
         return response;
      })
      .then((response) => dispatch(itemsFetchDataSuccess(response.data)))
   };
}

mapDispatchToProps()関数は、ディスパッチ関数をパラメーターとして受け取り、反応コンポーネントに渡すプレーンオブジェクトとしてコールバックプロパティを返します。

ここでは、反応リストコンポーネントのpropとしてfetchDataにアクセスできます。これは、API呼び出しを行うアクションをディスパッチします。 mapDispatchToProps()は、格納するアクションをディスパッチするために使用されます。 react-reduxでは、コンポーネントはストアに直接アクセスできません。 唯一の方法は、connect()を使用することです。

下の図を通して、react-reduxがどのように機能するかを理解しましょう-

React Redux Work

*STORE* -すべてのアプリケーションの状態をJavaScriptオブジェクトとして保存します
*PROVIDER* -店舗を利用可能にする
*CONTAINER* -アプリの状態を取得し、コンポーネントの小道具として提供する

コンポーネント-ユーザーはビューコンポーネントを介して対話します

アクション-ストアの変更を引き起こします。アプリの状態を変更する場合と変更しない場合があります

*REDUCER* -アプリの状態を変更し、状態とアクションを受け入れ、更新された状態を返す唯一の方法。

ただし、Reduxは独立したライブラリであり、任意のUIレイヤーで使用できます。 React-reduxは、公式のRedux、reactとのUIバインディングです。 また、Reduxアプリの適切なリアクションを促進します。 React-reduxはパフォーマンスの最適化を内部的に実装しているため、コンポーネントの再レンダリングは必要な場合にのみ行われます。

要約すると、Reduxは最短かつ最速のコードを書くようには設計されていません。 予測可能な状態管理コンテナを提供することを目的としています。 特定の状態がいつ変化したか、またはデータがどこから来たかを理解するのに役立ちます。

Redux-Reactの例

次に、反応とReduxアプリケーションの小さな例を示します。 小さなアプリの開発を試すこともできます。 増加または減少カウンターのサンプルコードは以下のとおりです-

これは、ストアの作成と、reactアプリコンポーネントのレンダリングを行うルートファイルです。

/src/index.js

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux';
import reducer from '../src/reducer/index'
import App from '../src/App'
import './index.css';

const store = createStore(
   reducer,
   window.__REDUX_DEVTOOLS_EXTENSION__ &&
   window.__REDUX_DEVTOOLS_EXTENSION__()
)
render(
   <Provider store = {store}>
      <App/>
   </Provider>, document.getElementById('root')
)

これは、reactのルートコンポーネントです。 カウンターコンテナコンポーネントを子としてレンダリングする役割を果たします。

/src/app.js

import React, { Component } from 'react';
import './App.css';
import Counter from '../src/container/appContainer';

class App extends Component {
   render() {
      return (
         <div className = "App">
            <header className = "App-header">
               <Counter/>
            </header>
         </div>
      );
   }
}
export default App;

以下は、コンポーネントを反応させるためにReduxの状態を提供する責任があるコンテナコンポーネントです-

/container/counterContainer.js

import { connect } from 'react-redux'
import Counter from '../component/counter'
import { increment, decrement, reset } from '../actions';

const mapStateToProps = (state) => {
   return {
      counter: state
   };
};
const mapDispatchToProps = (dispatch) => {
   return {
      increment: () => dispatch(increment()),
      decrement: () => dispatch(decrement()),
      reset: () => dispatch(reset())
   };
};
export default connect(mapStateToProps, mapDispatchToProps)(Counter);

以下は、ビューの一部を担当する反応コンポーネントです-

/component/counter.js
import React, { Component } from 'react';
class Counter extends Component {
   render() {
      const {counter,increment,decrement,reset} = this.props;
      return (
         <div className = "App">
            <div>{counter}</div>
            <div>
               <button onClick = {increment}>INCREMENT BY 1</button>
            </div>
            <div>
               <button onClick = {decrement}>DECREMENT BY 1</button>
            </div>
            <button onClick = {reset}>RESET</button>
         </div>
      );
   }
}
export default Counter;

以下は、アクションの作成を担当するアクション作成者です-

/actions/index.js
export function increment() {
   return {
      type: 'INCREMENT'
   }
}
export function decrement() {
   return {
      type: 'DECREMENT'
   }
}
export function reset() {
   return { type: 'RESET' }
}

以下に、Reduxの状態を更新するためのreducerファイルのコード行を示しました。

reducer/index.js
const reducer = (state = 0, action) => {
   switch (action.type) {
      case 'INCREMENT': return state + 1
      case 'DECREMENT': return state - 1
      case 'RESET' : return 0 default: return state
   }
}
export default reducer;

最初は、アプリは次のように見えます-

アプリの外観

インクリメントを2回クリックすると、出力画面は次のようになります-

出力画面

我々は一度それをデクリメントすると、次の画面が表示されます-

デクリメント

また、リセットすると、アプリは初期値に戻り、カウンター値0になります。 これは以下に示されています-

初期状態

最初のインクリメントアクションが行われたときにRedux開発ツールで何が起こるかを理解しましょう-

Redux Devtool

アプリの状態は、インクリメントアクションのみがディスパッチされ、残りのアクションがスキップされる時点に移動します。

自分で課題として小さなTodoアプリを開発し、Reduxツールをよりよく理解することをお勧めします。