redux-sagaを理解できない理由と使い方
Reduxの非同期処理を書くとき実装のしやすさからredux-thunkを選ぶことがありますが、大規模な開発になるとredux-sagaの方が良いのでその実装方法について書きます。
これから書くことはほぼUdemyの講座「Redux Saga (with React and Redux): Fast-track intro course」の内容を元にしています。
redux-thunkについてはこちらでまとめています。
「Redux Thunkでactionに非同期処理を書く」
redux-sagaをなかなか理解できない理由
reduxの非同期処理でredux-sagaを使おうと導入したら挫折した経験はありませんか?
function* watchGetUsersRequest() {
yield takeEvery(actions.Types.GET_USERS_REQUEST, getUsers);
}
function関数の末尾の*
。「このアスタリスクは何だ?」と思いませんでしたか?
またyield
についても「なんだこれは??」と。
これはECMAScript6から使えるようになったgenerator
関数の書き方になります。
そのgenerator
は戻り値としてiterator
を返します。
iterator
もECMAScript6から使えるようになりました。
generatorについて
- 関数になります。functionの末尾に
*
を使って宣言します - 関数の中で
yield
を使います - iteratorのnext関数を使って処理を行います
function* hoge() {
yield 1;
}
let iter = hoge();
iter.next(); // { done: false, value: 1 } valueの「1」はyieldで指定した「1」です
iter.next(); // { done: true, value: undefined } 呼び出すデータがなくなったのでdoneがtrueになっています
-
yield
は何回も使うことができます - next関数を使うとyieldで処理が止まります
function* hoge() {
console.log('い');
console.log('ろ');
yield 1;
console.log('は');
yield 2;
console.log('に');
console.log('ほ');
console.log('へ');
yield 3;
}
let iter = hoge();
iter.next();
/*
"い"
"ろ"
{
done: false,
value: 1
}
*/
iter.next();
/*
"は"
{
done: false,
value: 2
}
*/
iter.next();
/*
"に"
"ほ"
"へ"
{
done: false,
value: 3
}
*/
iter.next();
/*
{
done: true,
value: undefined
}
*/
redux-sagaはgenerator関数を使ってyieldごとに処理が行われることが分かります。
effectsの説明
redux-sagaには非同期処理を行う上で使用する関数があります。
それらを理解するために処理について説明します。
- takeEvery
- takeLatest
- take
- call
- put
takeEvery
- actionがdispatchされる度に監視させたい処理
- 例) APIからデータのリストを取得するとき
function* watchGetUsersRequest(){
yield takeEvery(action.Types.GET_USERS_REQUEST, getUsers);
}
takeLatest
- actionが複数回dispatchされる可能性があるとき、現在実行中の最新のsagaのみを取得する処理
- 例) レコードの作成または更新
- 例) 同時に複数のコンポーネントから同じAPIエンドポイントを参照するとき
function* watchCreateUserRequest() {
yield takeLatest(actions.Types.CREATE_USER_REQUEST, createUser);
}
take
- 実行中のsagaが完了するまで、そのactionがdispatchされるタイミングを監視するとき
- 例) ユーザーの削除
- 例) 進行中の処理が完了するのを待ってから、別の処理を行うとき
function* watchDeleteUserRequest() {
const action = yield take(actions.Types.DELETE_USER_REQUEST);
yield call(deleteUser, {
userId: action.payload.userId,
});
}
call
- 関数またはPromiseを呼び出すとき、その関数またはPromiseの実行が完了するのを待ってから次のコード行を実行するとき
- Promiseの完了を待つ
function* deleteUser({ userId }){
try {
const result = yield call(api.deleteUser, userId);
} catch(e) {}
}
function* watchDeleteUserRequest(){
while(true){
const { userId } = yield take(action.Types.DELETE_USER_REQUEST);
yield call(deleteUser, { userId });
}
}
put
- actionをdispatchするとき
- 例) APIから受け取ったdataで更新するとき
- 例) error処理を行うとき
function* getUsers() {
try {
const result = yield call(api.getUsers);
yield put(actions.getUsersSuccess({
users: result.data.users
}));
} catch (e) {
yield put(actions.usersError({
error: 'An error occurred when trying to get the users',
}));
}
}
}
Set Up
redux-sagaはreduxのmiddlewareになるのでcreateStore
に設定する必要があります。
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootSaga from './sagas';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(reducers, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(rootSaga);
rootSagaの設定
- rootSagaでsagaの処理を配列で管理する。allはPromiseAllと同様の処理
import { all } from 'redux-saga/effects';
import usersSagas from './users';
export default function* rootSaga() {
yield all([
...usersSagas,
]);
};
- 各非同期処理をforkでアタッチさせる。(この説明は合ってるのか。。?)
import { fork } from 'redux-saga/effects';
const usersSagas = [
fork(watchGetUsersRequest),
fork(watchCreateUserRequest),
fork(watchDeleteUserRequest),
];
export default usersSagas;
CRUD処理を書いてみる
- ユーザーのIDと名前を管理してます
- errorハンドリングしています
- createとdelete処理を行った後、
yield call(getUsers)
でユーザー一覧を更新してます
共通処理
action
export const Types = {
GET_USERS_REQUEST: 'users/get_users_request',
GET_USERS_SUCCESS: 'users/get_users_success',
CREATE_USER_REQUEST: 'users/create_user_request',
DELETE_USER_REQUEST: 'users/delete_user_request',
USERS_ERROR: 'users/user_error',
};
export const getUsersRequest = () => ({
type: Types.GET_USERS_REQUEST,
});
export const getUsersSuccess = ({ items }) => ({
type: Types.GET_USERS_SUCCESS,
payload: {
items,
},
});
export const createUserRequest = ({ name }) => ({
type: Types.CREATE_USER_REQUEST,
payload: {
name,
},
});
export const deleteUserRequest = (userId) => ({
type: Types.DELETE_USER_REQUEST,
payload: {
userId,
},
});
export const usersError = ({ error }) => ({
type: Types.USERS_ERROR,
payload: {
error,
},
});
api
import axios from 'axios';
export const getUsers = () => {
return axios.get('/users');
};
export const createUser = ({ mame }) => {
return axios.post('/users', {
name,
});
};
export const deleteUser = (userId) => {
return axios.delete(`/users/${userId}`);
};
reducers
import { Types } from '../actions/users';
const INITIAL_STATE = {
items: [],
error: '',
};
export default function users (state = INITIAL_STATE, action) {
switch(action.type) {
case Types.GET_USERS_SUCCESS: {
return {
...state,
items: action.payload.items,
};
}
case Types.USERS_ERROR: {
return {
...state,
error: action.payload.error,
}
}
default: {
return state;
}
}
}
Sagaの処理
read
function* getUsers() {
try {
const result = yield call(api.getUsers);
yield put(actions.getUsersSuccess({
items: result.data.data,
}));
}
catch (e) {
yield put(actions.usersError({
error: 'An error occurred when trying to get the users',
}));
}
}
function* watchGetUsersRequest() {
// actionの処理をしてからgenerator関数のgetUsersを行う
yield takeEvery(actions.Types.GET_USERS_REQUEST, getUsers);
}
create
// payloadはaction、createUserRequestから取得
function* createUser({ payload }) {
try {
yield call(api.createUser, {
name: payload.name,
});
yield call(getUsers); // ユーザー一覧を更新
} catch (e) {
yield put(actions.usersError({
error: 'An error occurred when trying to create the user',
}));
}
}
function* watchCreateUserRequest() {
// actionの処理をしてからgenerator関数のcreateUserを行う
yield takeLatest(actions.Types.CREATE_USER_REQUEST, createUser);
}
delete
function* deleteUser({ userId }) {
try {
yield call(api.deleteUser, userId);
yield call(getUsers); // ユーザー一覧を更新
} catch (e) {
yield put(actions.usersError({
error: 'An error occurred when trying to delete the user',
}));
}
}
function* watchDeleteUserRequest() {
const action = yield take(actions.Types.DELETE_USER_REQUEST);
yield call(deleteUser, {
userId: action.payload.userId,
});
}
以上になります。
参考サイト
Author And Source
この問題について(redux-sagaを理解できない理由と使い方), 我々は、より多くの情報をここで見つけました https://qiita.com/macotok/items/ec5460ac17f5a20c4735著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .