序章
デフォルトでは、Reduxのアクションは同期的にディスパッチされます。これは、外部APIと通信したり、副作用を実行したりする必要がある重要なアプリにとっては問題です。 Reduxは、ディスパッチされるアクションとレデューサーに到達するアクションの間にあるミドルウェアも許可します。
副作用と非同期アクションを可能にする2つの非常に人気のあるミドルウェアライブラリがあります: ReduxThunkとReduxSaga。 この投稿では、ReduxThunkについて説明します。
Thunk は、関数を使用して演算の評価/計算を遅らせるプログラミングの概念です。
Redux Thunkは、アクションオブジェクトの代わりに関数を返すアクションクリエーターを呼び出すことができるミドルウェアです。 その関数はストアのディスパッチメソッドを受け取ります。このメソッドは、非同期操作が完了すると、関数の本体内で通常の同期アクションをディスパッチするために使用されます。
この記事では、Redux Thunkを追加する方法と、それが架空のTodoアプリケーションにどのように適合するかを学習します。
前提条件
この投稿は、ReactとReduxの基本的な知識があることを前提としています。 Reduxを使い始めた場合は、この投稿を参照できます。
このチュートリアルは、実行する必要があり、完了したタスクを追跡する架空のTodoアプリケーションを基に構築されています。 create-react-app
を使用して新しいReactアプリケーションを生成し、redux
、react-redux
、およびaxios
が既にインストールされていると推測できます。
Todoアプリケーションを最初から作成する方法の詳細については、ここでは説明しません。 これは、ReduxThunkを強調するための概念的な設定として提示されています。
redux-thunk
を追加
まず、ターミナルを使用してプロジェクトディレクトリに移動し、プロジェクトにredux-thunk
パッケージをインストールします。
- npm install redux-thunk@2.3.0
注:ReduxThunkはわずか14行のコードです。 Reduxミドルウェアが内部でどのように機能するかについては、ソースを確認してください。
次に、ReduxのapplyMiddleware
を使用してアプリのストアを作成するときに、ミドルウェアを適用します。 redux
とreact-redux
を使用するReactアプリケーションを考えると、index.js
ファイルは次のようになります。
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import './index.css';
import rootReducer from './reducers';
import App from './App';
import * as serviceWorker from './serviceWorker';
// use applyMiddleware to add the thunk middleware to the store
const store = createStore(rootReducer, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
これで、Redux Thunkがインポートされ、アプリケーションに適用されます。
サンプルアプリケーションでのReduxThunkの使用
Redux Thunkの最も一般的な使用例は、外部APIと非同期で通信して、データを取得または保存する場合です。 Redux Thunkを使用すると、リクエストのライフサイクルに従うアクションを外部APIに簡単にディスパッチできます。
通常、新しいToDoアイテムを作成するには、最初にアクションをディスパッチして、Todoアイテムの作成が開始されたことを示します。 次に、todoアイテムが正常に作成され、外部サーバーによって返された場合、新しいtodoアイテムを使用して別のアクションをディスパッチします。 エラーが発生し、todoがサーバーに保存されない場合は、代わりにエラーのあるアクションをディスパッチできます。
ReduxThunkを使用してこれがどのように達成されるかを見てみましょう。
コンテナコンポーネントで、アクションをインポートしてディスパッチします。
import { connect } from 'react-redux';
import { addTodo } from '../actions';
import NewTodo from '../components/NewTodo';
const mapDispatchToProps = dispatch => {
return {
onAddTodo: todo => {
dispatch(addTodo(todo));
}
};
};
export default connect(
null,
mapDispatchToProps
)(NewTodo);
このアクションは、 Axios を利用して、POST
リクエストをJSONPlaceholder ( https://jsonplaceholder.typicode.com/todos [ X162X]):
import {
ADD_TODO_SUCCESS,
ADD_TODO_FAILURE,
ADD_TODO_STARTED,
DELETE_TODO
} from './types';
import axios from 'axios';
export const addTodo = ({ title, userId }) => {
return dispatch => {
dispatch(addTodoStarted());
axios
.post(`https://jsonplaceholder.typicode.com/todos`, {
title,
userId,
completed: false
})
.then(res => {
dispatch(addTodoSuccess(res.data));
})
.catch(err => {
dispatch(addTodoFailure(err.message));
});
};
};
const addTodoSuccess = todo => ({
type: ADD_TODO_SUCCESS,
payload: {
...todo
}
});
const addTodoStarted = () => ({
type: ADD_TODO_STARTED
});
const addTodoFailure = error => ({
type: ADD_TODO_FAILURE,
payload: {
error
}
});
addTodo
アクションクリエーターが通常のアクションオブジェクトの代わりに関数を返す方法に注目してください。 その関数は、ストアからディスパッチメソッドを受け取ります。
関数の本体内で、最初にストアに即時同期アクションをディスパッチして、外部APIを使用してToDoの保存を開始したことを示します。 次に、Axiosを使用して、サーバーに対して実際のPOST
要求を行います。 サーバーからの正常な応答では、応答から受信したデータを使用して同期成功アクションをディスパッチしますが、失敗応答では、エラーメッセージを使用して別の同期アクションをディスパッチします。
この場合のJSONPlaceholderのような外部APIを使用すると、実際に発生しているネットワーク遅延を確認できます。 ただし、ローカルバックエンドサーバーを使用している場合は、ネットワーク応答が速すぎて実際のユーザーが経験するネットワーク遅延を経験できない可能性があるため、開発時に人為的な遅延を追加できます。
// ...
export const addTodo = ({ title, userId }) => {
return dispatch => {
dispatch(addTodoStarted());
axios
.post(ENDPOINT, {
title,
userId,
completed: false
})
.then(res => {
setTimeout(() => {
dispatch(addTodoSuccess(res.data));
}, 2500);
})
.catch(err => {
dispatch(addTodoFailure(err.message));
});
};
};
// ...
エラーシナリオをテストするには、手動でエラーをスローします。
// ...
export const addTodo = ({ title, userId }) => {
return dispatch => {
dispatch(addTodoStarted());
axios
.post(ENDPOINT, {
title,
userId,
completed: false
})
.then(res => {
throw new Error('addToDo error!');
// dispatch(addTodoSuccess(res.data));
})
.catch(err => {
dispatch(addTodoFailure(err.message));
});
};
};
// ...
完全を期すために、リクエストのライフサイクル全体を処理するためにtodoレデューサーがどのように見えるかの例を次に示します。
import {
ADD_TODO_SUCCESS,
ADD_TODO_FAILURE,
ADD_TODO_STARTED,
DELETE_TODO
} from '../actions/types';
const initialState = {
loading: false,
todos: [],
error: null
};
export default function todosReducer(state = initialState, action) {
switch (action.type) {
case ADD_TODO_STARTED:
return {
...state,
loading: true
};
case ADD_TODO_SUCCESS:
return {
...state,
loading: false,
error: null,
todos: [...state.todos, action.payload]
};
case ADD_TODO_FAILURE:
return {
...state,
loading: false,
error: action.payload.error
};
default:
return state;
}
}
探索中getState
状態からディスパッチメソッドを受け取ることに加えて、Redux Thunkを使用して非同期アクションクリエーターによって返される関数は、ストアのgetState
メソッドも受け取るため、現在のストア値を読み取ることができます。
export const addTodo = ({ title, userId }) => {
return (dispatch, getState) => {
dispatch(addTodoStarted());
console.log('current state:', getState());
// ...
};
};
上記の場合、現在の状態がコンソールに出力されます。
例えば:
{loading: true, todos: Array(1), error: null}
getState
を使用すると、現在の状態に応じて処理を変えることができます。 たとえば、アプリを一度に4つのtodoアイテムのみに制限する場合、状態にすでに最大量のtodoアイテムが含まれていると、関数から戻ることができます。
export const addTodo = ({ title, userId }) => {
return (dispatch, getState) => {
const { todos } = getState();
if (todos.length > 4) return;
dispatch(addTodoStarted());
// ...
};
};
上記により、アプリは4つのtodoアイテムに制限されます。
結論
このチュートリアルでは、ReactアプリケーションにRedux Thunkを追加して、アクションを非同期でディスパッチできるようにする方法について説明しました。 これは、Reduxストアを利用して外部APIに依存している場合に役立ちます。
Reactの詳細については、 React.js シリーズのコーディング方法をご覧になるか、Reactトピックページで演習やプログラミングプロジェクトを確認してください。