Redux-クイックガイド
Reduxは、JavaScriptアプリの予測可能な状態コンテナーです。アプリケーションが大きくなると、アプリケーションを整理してデータフローを維持することが難しくなります。Reduxは、Storeと呼ばれる単一のグローバルオブジェクトでアプリケーションの状態を管理することにより、この問題を解決します。Reduxの基本原則は、アプリケーション全体の一貫性を維持するのに役立ち、デバッグとテストを容易にします。
さらに重要なことに、タイムトラベルデバッガーと組み合わせたライブコード編集を提供します。React、Angular、Vueなどの任意のビューレイヤーに柔軟に対応できます。
Reduxの原則
Reduxの予測可能性は、以下に示す3つの最も重要な原則によって決定されます-
信頼できる唯一の情報源
アプリケーション全体の状態は、単一のストア内のオブジェクトツリーに保存されます。アプリケーション全体の状態が単一のツリーに保存されるため、デバッグが容易になり、開発が高速になります。
状態は読み取り専用です
状態を変更する唯一の方法は、何が起こったかを説明するオブジェクトであるアクションを発行することです。これは、誰もアプリケーションの状態を直接変更できないことを意味します。
変更は純粋関数で行われます
アクションによって状態ツリーがどのように変換されるかを指定するには、純粋なレデューサーを記述します。レデューサーは、状態の変更が行われる中心的な場所です。レデューサーは、状態とアクションを引数として取り、新しく更新された状態を返す関数です。
Reduxをインストールする前に、 we have to install Nodejs and NPM。以下は、インストールに役立つ手順です。NodejsとNPMがデバイスにすでにインストールされている場合は、これらの手順をスキップできます。
訪問 https://nodejs.org/ パッケージファイルをインストールします。
インストーラーを実行し、指示に従い、使用許諾契約に同意します。
デバイスを再起動して実行します。
コマンドプロンプトを開き、node -vと入力すると、インストールが成功したことを確認できます。これにより、システム内のノードの最新バージョンが表示されます。
npmが正常にインストールされているかどうかを確認するには、npm –vと入力すると、最新のnpmバージョンが返されます。
reduxをインストールするには、以下の手順に従ってください-
コマンドプロンプトで次のコマンドを実行して、Reduxをインストールします。
npm install --save redux
反応アプリケーションでReduxを使用するには、次のように追加の依存関係をインストールする必要があります-
npm install --save react-redux
Reduxの開発者ツールをインストールするには、依存関係として以下をインストールする必要があります-
コマンドプロンプトで以下のコマンドを実行して、Reduxdev-toolsをインストールします。
npm install --save-dev redux-devtools
Redux開発ツールをインストールしてプロジェクトに統合したくない場合は、インストールできます Redux DevTools Extension ChromeとFirefoxの場合。
アプリケーションの状態が次のようなプレーンオブジェクトによって記述されていると仮定します。 initialState これは次のとおりです-
const initialState = {
isLoading: false,
items: [],
hasError: false
};
アプリケーション内のすべてのコードでこの状態を変更することはできません。状態を変更するには、アクションをディスパッチする必要があります。
アクションとは何ですか?
アクションは、typeプロパティで変更を引き起こす意図を説明するプレーンオブジェクトです。実行されているアクションのタイプを示す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の3つの原則としての制限についてはすでに知っています。次の図は、Reduxのデータフローをよりよく理解するのに役立ちます-
ユーザーがアプリケーションを操作すると、アクションがディスパッチされます。
ルートレデューサー関数は、現在の状態とディスパッチされたアクションで呼び出されます。ルートレデューサーは、タスクをより小さなレデューサー関数に分割し、最終的に新しい状態を返す場合があります。
ストアは、コールバック関数を実行してビューに通知します。
ビューは、更新された状態を取得して、再レンダリングできます。
ストアは、Reduxの不変のオブジェクトツリーです。ストアは、アプリケーションの状態を保持する状態コンテナーです。Reduxは、アプリケーションに1つのストアしか持つことができません。Reduxでストアを作成するときはいつでも、レデューサーを指定する必要があります。
を使用してストアを作成する方法を見てみましょう createStoreReduxのメソッド。以下に示すように、ストア作成プロセスをサポートする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の公式ドキュメントによると、アクションはストアの唯一の情報源です。アプリケーションからストアに情報のペイロードを運びます。
前に説明したように、アクションはプレーンなJavaScriptオブジェクトであり、実行されるアクションのタイプを示すtype属性が必要です。何が起こったのかを教えてくれます。タイプは、以下に示すように、アプリケーションで文字列定数として定義する必要があります-
const ITEMS_REQUEST = 'ITEMS_REQUEST';
このtype属性とは別に、アクションオブジェクトの構造は完全に開発者次第です。アクションオブジェクトをできるだけ軽くし、必要な情報のみを渡すことをお勧めします。
ストアに変更を加えるには、最初にstore.dispatch()関数を使用してアクションをディスパッチする必要があります。アクションオブジェクトは次のとおりです-
{ type: GET_ORDER_STATUS , payload: {orderId,userId } }
{ type: GET_WISHLIST_ITEMS, payload: userId }
アクションクリエーター
アクションクリエーターは、アクションオブジェクトの作成プロセスをカプセル化する関数です。これらの関数は、アクションであるプレーンなJsオブジェクトを返すだけです。クリーンなコードの記述を促進し、再利用性の実現に役立ちます。
アクションをディスパッチできるアクションクリエーターについて学びましょう。 ‘ITEMS_REQUEST’製品アイテムリストデータをサーバーから要求します。一方、isLoading 'ITEMS_REQUEST'アクションタイプのレデューサーで状態がtrueになり、アイテムが読み込まれていることを示しますが、サーバーからデータがまだ受信されていません。
当初、isLoading状態は initialState何もロードされていないと仮定したオブジェクト。ブラウザでデータを受信すると、対応するレデューサーの「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()を直接使用して、アクションをディスパッチできます。ただし、react-Reduxヘルパーメソッドと呼ばれる方法でアクセスする可能性が高くなりますconnect()。使用することもできますbindActionCreators() 多くのアクションクリエーターをディスパッチ機能でバインドするメソッド。
関数は、引数と呼ばれる入力を受け取り、戻り値と呼ばれる出力を生成するプロセスです。関数が次の規則に従う場合、関数は純粋と呼ばれます-
関数は、同じ引数に対して同じ結果を返します。
その評価には副作用はありません。つまり、入力データは変更されません。
ローカル変数とグローバル変数の変更はありません。
グローバル変数のように外部状態に依存しません。
関数への入力として渡された値の2倍を返す関数の例を見てみましょう。一般に、f(x)=> x * 2と記述されます。関数が引数値2で呼び出された場合、出力は4、f(2)=> 4になります。
以下に示すように、JavaScriptで関数の定義を書いてみましょう-
const double = x => x*2; // es6 arrow function
console.log(double(2)); // 4
Here, double is a pure function.
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 or false’。これにより、ユーザーが同じ製品を複数回追加することを防ぎます。ここで、新しいオブジェクトを返す代わりに、上記のような状態でisAddedToCartプロパティを変更しています。カートにアイテムを追加しようとしても、何も起こりません。カートに追加ボタンは無効になりません。
この動作の理由は次のとおりです-
Reduxは、両方のオブジェクトのメモリ位置によって古いオブジェクトと新しいオブジェクトを比較します。変更が発生した場合は、レデューサーからの新しいオブジェクトが必要です。また、変更が発生しなければ、古いオブジェクトを取り戻すことも期待しています。この場合も同じです。このため、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で状態を変更する唯一の方法はレデューサーです。それはあなたが論理と計算を書くことができる唯一の場所です。レデューサー関数は、ディスパッチされているアプリとアクションの以前の状態を受け入れ、次の状態を計算して、新しいオブジェクトを返します。
次のいくつかのことは、レデューサー内で実行しないでください-
- 関数引数の突然変異
- 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ページを設計するとします。ロジックを異なるレデューサーファイルに分離し、それらを独立して機能させることができます。ある注文IDとユーザーIDに対応する注文のステータスを取得するためにGET_ORDER_STATUSアクションがディスパッチされたと仮定します。
/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のcombineReducersユーティリティを使用して両方のレデューサーを組み合わせることができます。CombineReducersは、値が異なるレデューサー関数であるオブジェクトを返す関数を生成します。すべてのレデューサーをインデックスレデューサーファイルにインポートし、それぞれの名前を持つオブジェクトとして組み合わせることができます。
/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自体は同期しているので、 async 次のような操作 network requestReduxで動作しますか?ここではミドルウェアが便利です。前に説明したように、レデューサーはすべての実行ロジックが記述される場所です。レデューサーは、誰がそれを実行するか、アクションがディスパッチされる前後にアプリの状態を記録するのにかかる時間とは関係ありません。
この場合、Reduxミドルウェア機能は、ディスパッチされたアクションがレデューサーに到達する前にそれらと対話するための媒体を提供します。カスタマイズされたミドルウェア関数は、一部のロジックをラップアラウンドする高階関数(別の関数を返す関数)を作成することで作成できます。複数のミドルウェアを組み合わせて新しい機能を追加することができ、各ミドルウェアは前後に何が起こったかについての知識を必要としません。ディスパッチされたアクションとレデューサーの間のどこかにミドルウェアを想像することができます。
一般に、ミドルウェアはアプリの非同期アクションを処理するために使用されます。ReduxはapplyMiddlewareと呼ばれるAPIを提供します。これにより、カスタムミドルウェアだけでなく、redux-thunkやredux-promiseなどのReduxミドルウェアを使用できます。ミドルウェアをストアに適用します。applyMiddlewareAPIを使用する構文は次のとおりです。
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-Devtoolsは、Reduxアプリのデバッグプラットフォームを提供します。これにより、タイムトラベルデバッグとライブ編集を実行できます。公式ドキュメントの機能の一部は次のとおりです-
これにより、すべての状態とアクションペイロードを検査できます。
アクションを「キャンセル」することで、時間を遡ることができます。
レデューサーコードを変更すると、「ステージングされた」各アクションが再評価されます。
レデューサーがスローした場合、エラーと、これが発生したアクションを特定できます。
persistState()ストアエンハンサーを使用すると、ページのリロードを超えてデバッグセッションを永続化できます。
以下に示すように、Reduxdev-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開発ツールを使用して、アクションをスキップして時間を遡る方法を確認しましょう。次のスクリーンショットは、アイテムのリストを取得するために以前にディスパッチしたアクションについて説明しています。ここでは、インスペクタータブでディスパッチされたアクションを確認できます。右側には、状態ツリーの違いを示す[デモ]タブが表示されます。
このツールを使い始めると、このツールに慣れることができます。このReduxプラグインツールから、実際のコードを記述せずにアクションをディスパッチできます。最後の行のディスパッチャオプションは、これに役立ちます。アイテムが正常にフェッチされた最後のアクションを確認しましょう。
サーバーからの応答としてオブジェクトの配列を受け取りました。すべてのデータは、当社のページにリストを表示するために利用できます。右上の[状態]タブをクリックして、ストアの状態を同時に追跡することもできます。
前のセクションでは、タイムトラベルのデバッグについて学習しました。ここで、1つのアクションをスキップして、時間を遡ってアプリの状態を分析する方法を確認しましょう。アクションタイプをクリックすると、「ジャンプ」と「スキップ」の2つのオプションが表示されます。
特定のアクションタイプのスキップボタンをクリックすると、特定のアクションをスキップできます。アクションが発生しなかったかのように動作します。特定のアクションタイプでジャンプボタンをクリックすると、そのアクションが発生したときの状態に移動し、残りのすべてのアクションを順番にスキップします。このようにして、特定のアクションが発生したときの状態を保持できます。この機能は、アプリケーションのデバッグとエラーの検出に役立ちます。
最後のアクションをスキップし、バックグラウンドからのすべてのリストデータが消えました。アイテムのデータが到着しておらず、アプリにページにレンダリングするデータがない場合に戻ります。実際には、コーディングとデバッグが簡単になります。
ほとんどの場合関数を作成するため、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
}
最終的に、 run npm test or 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)
})
})
レデューサーのテストケース
アクションが適用されると、レデューサーは新しい状態を返す必要があることを学びました。したがって、レデューサーはこの動作でテストされます。
以下に示すようなレデューサーを検討してください-
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
})
})
})
テストケースの作成に慣れていない場合は、JESTの基本を確認できます。
前の章では、Reduxとは何か、そしてそれがどのように機能するかを学びました。ビューパーツとReduxの統合を確認しましょう。Reduxには任意のビューレイヤーを追加できます。また、reactライブラリとReduxについても説明します。
さまざまなreactコンポーネントが、最上位のコンポーネントから最下位のコンポーネントまでのすべてのコンポーネントに小道具として渡さずに、同じデータをさまざまな方法で表示する必要がある場合を考えてみましょう。反応コンポーネントの外に保管するのが理想的です。さまざまなコンポーネントにデータを渡す必要がないため、データの取得を高速化するのに役立ちます。
Reduxでそれがどのように可能であるかを議論しましょう。Reduxはreact-reduxパッケージを提供し、以下に示す2つのユーティリティでreactコンポーネントをバインドします-
- Provider
- Connect
プロバイダーは、ストアをアプリケーションの残りの部分で使用できるようにします。接続機能は、コンポーネントがストアに接続するのに役立ち、ストアの状態で発生する各変更に応答します。
見てみましょう root index.js ストアを作成し、react-reduxアプリ内のアプリの残りの部分にストアを有効にするプロバイダーを使用するファイル。
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()関数は、ディスパッチ関数をパラメーターとして受け取り、reactコンポーネントに渡すプレーンオブジェクトとしてコールバック小道具を返します。
ここでは、API呼び出しを行うためのアクションをディスパッチするreactリストコンポーネントの小道具としてfetchDataにアクセスできます。mapDispatchToProps()は、保存するアクションをディスパッチするために使用されます。react-reduxでは、コンポーネントはストアに直接アクセスできません。唯一の方法はconnect()を使用することです。
下の図からreact-reduxがどのように機能するかを理解しましょう-
STORE −すべてのアプリケーションの状態をJavaScriptオブジェクトとして保存します
PROVIDER −店舗を利用可能にします
CONTAINER −アプリの状態を取得し、コンポーネントへの小道具として提供します
COMPONENT −ユーザーはビューコンポーネントを介して対話します
ACTIONS −ストアに変更が発生し、アプリの状態が変更される場合と変更されない場合があります
REDUCER −アプリの状態を変更し、状態とアクションを受け入れ、更新された状態を返す唯一の方法。
ただし、Reduxは独立したライブラリであり、任意のUIレイヤーで使用できます。React-reduxは公式のReduxであり、ReactとのUIバインディングです。さらに、それは良い反応のReduxアプリ構造を促進します。React-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);
以下に示すのは、ビューパーツを担当するreactコンポーネントです。
/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開発ツールで何が起こるかを理解しましょう-
アプリの状態は、インクリメントアクションのみがディスパッチされ、残りのアクションがスキップされる時間に移動します。
小さなTodoアプリを自分で課題として開発し、Reduxツールをよりよく理解することをお勧めします。