[解決済み] ES6 ジェネレータで redux-saga を使用する利点/欠点と ES2017 async/await で redux-thunk を使用する利点/欠点
質問
今、redux townの最新の子供が話題になっていますね。 レデュックス・サーガ/REDUX-SAGA . これは、アクションをリッスン/ディスパッチするためのジェネレータ関数を使用します。
を使うことの長所と短所を教えてください。
redux-saga
を使用する以下のアプローチではなく
redux-thunk
をasync/awaitで使用します。
コンポーネントは次のようなもので、通常通りアクションをディスパッチします。
import { login } from 'redux/auth';
class LoginForm extends Component {
onClick(e) {
e.preventDefault();
const { user, pass } = this.refs;
this.props.dispatch(login(user.value, pass.value));
}
render() {
return (<div>
<input type="text" ref="user" />
<input type="password" ref="pass" />
<button onClick={::this.onClick}>Sign In</button>
</div>);
}
}
export default connect((state) => ({}))(LoginForm);
すると、私のアクションは次のようになります。
// auth.js
import request from 'axios';
import { loadUserData } from './user';
// define constants
// define initial state
// export default reducer
export const login = (user, pass) => async (dispatch) => {
try {
dispatch({ type: LOGIN_REQUEST });
let { data } = await request.post('/login', { user, pass });
await dispatch(loadUserData(data.uid));
dispatch({ type: LOGIN_SUCCESS, data });
} catch(error) {
dispatch({ type: LOGIN_ERROR, error });
}
}
// more actions...
// user.js
import request from 'axios';
// define constants
// define initial state
// export default reducer
export const loadUserData = (uid) => async (dispatch) => {
try {
dispatch({ type: USERDATA_REQUEST });
let { data } = await request.get(`/users/${uid}`);
dispatch({ type: USERDATA_SUCCESS, data });
} catch(error) {
dispatch({ type: USERDATA_ERROR, error });
}
}
// more actions...
解決方法は?
redux-sagaでは、上記の例に相当するものは次のようになります。
export function* loginSaga() {
while(true) {
const { user, pass } = yield take(LOGIN_REQUEST)
try {
let { data } = yield call(request.post, '/login', { user, pass });
yield fork(loadUserData, data.uid);
yield put({ type: LOGIN_SUCCESS, data });
} catch(error) {
yield put({ type: LOGIN_ERROR, error });
}
}
}
export function* loadUserData(uid) {
try {
yield put({ type: USERDATA_REQUEST });
let { data } = yield call(request.get, `/users/${uid}`);
yield put({ type: USERDATA_SUCCESS, data });
} catch(error) {
yield put({ type: USERDATA_ERROR, error });
}
}
まず注目すべきは、apiの関数をフォームを使って呼び出していることです。
yield call(func, ...args)
.
call
のようなプレーンなオブジェクトを作成するだけで、エフェクトは実行されません。
{type: 'CALL', func, args}
. 実行はredux-sagaミドルウェアに委ねられ、関数を実行し、その結果でジェネレータを再開するように管理されます。
主な利点は、単純な等式チェックを使用して、Reduxの外部でジェネレータをテストできることです。
const iterator = loginSaga()
assert.deepEqual(iterator.next().value, take(LOGIN_REQUEST))
// resume the generator with some dummy action
const mockAction = {user: '...', pass: '...'}
assert.deepEqual(
iterator.next(mockAction).value,
call(request.post, '/login', mockAction)
)
// simulate an error result
const mockError = 'invalid user/password'
assert.deepEqual(
iterator.throw(mockError).value,
put({ type: LOGIN_ERROR, error: mockError })
)
モックされたデータを単純に
next
メソッドを使用します。データをモックするのは、関数をモックするよりもずっと簡単です。
次に注目すべきは
yield take(ACTION)
. サンクは、アクションの作成者が新しいアクションのたびに呼び出します (例.
LOGIN_REQUEST
つまり、アクションは継続的に
プッシュ
をサンクに送り、サンクはいつそのアクションの処理をやめるかをコントロールできない。
redux-sagaでは、ジェネレータ
引っ張る
つまり、あるアクションをいつリッスンするか、しないかをコントロールすることができます。上の例では、フロー指示は
while(true)
ループに入るので、アクションが来るたびにリッスンすることになり、サンクのプッシュ動作に多少似ています。
プルアプローチでは、複雑な制御フローを実装することができます。例えば、次のような要件を追加したいとします。
-
LOGOUT ユーザーアクションの処理
-
ログインに成功すると、サーバーはトークンを返し、その期限は
expires_in
フィールドがあります。フィールドのたびにバックグラウンドで認証をリフレッシュする必要があります。expires_in
ミリ秒 -
api呼び出しの結果を待つ間(初回ログインまたは更新のいずれか)、ユーザーがその間にログアウトする可能性があることを考慮してください。
サンクを使用して、フロー全体のテストカバレッジを確保しながら、どのようにそれを実装するのでしょうか?Sagasを使うと、こんな感じになります。
function* authorize(credentials) {
const token = yield call(api.authorize, credentials)
yield put( login.success(token) )
return token
}
function* authAndRefreshTokenOnExpiry(name, password) {
let token = yield call(authorize, {name, password})
while(true) {
yield call(delay, token.expires_in)
token = yield call(authorize, {token})
}
}
function* watchAuth() {
while(true) {
try {
const {name, password} = yield take(LOGIN_REQUEST)
yield race([
take(LOGOUT),
call(authAndRefreshTokenOnExpiry, name, password)
])
// user logged out, next while iteration will wait for the
// next LOGIN_REQUEST action
} catch(error) {
yield put( login.error(error) )
}
}
}
上記の例では、並行処理に関する要求事項を
race
. もし
take(LOGOUT)
がレースに勝利した場合(すなわち、ユーザーがログアウトボタンをクリックした場合)。レースは自動的にキャンセルされ
authAndRefreshTokenOnExpiry
バックグラウンドタスクです。そして、もし
authAndRefreshTokenOnExpiry
の途中でブロックされました。
call(authorize, {token})
の呼び出しもキャンセルされます。キャンセルは自動的に下に伝搬します。
を見つけることができます。 上記のフローの実行可能なデモ
関連
-
Vueがechartsのtooltipにクリックイベントを追加するケーススタディ
-
vueが定義するプライベートフィルタと基本的な使い方
-
[解決済み】React - uncaught TypeError: 未定義のプロパティ 'setState' を読み取れない
-
[解決済み] 配列の結合時に未定義のプロパティ 'push' を読み込むことができない
-
[解決済み】GETできない / Nodejsエラー
-
[解決済み】React Nativeアプリをターミナルから実行するとエラーが発生する(iOS)
-
[解決済み] 期待される代入または関数呼び出し: 未使用式なし ReactJS
-
JavaScriptのStringに関する共通メソッド
-
OSSアップロードエラーを解決する: net::ERR_SSL_PROTOCOL_ERROR
-
[解決済み] forEachループでasync/awaitを使用する
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
vue3レスポンシブ対応のためのsetup+ref+reactive
-
親子コンポーネント通信を解決する3つのVueスロット
-
vueネットワークリクエストソリューション ネイティブネットワークリクエストとjsネットワークリクエストライブラリ
-
WeChatアプレット用ユニアプリによるグローバルシェアリング
-
Vueのフィルタの説明
-
[解決済み】リソースの読み込みに失敗した:Bind関数でサーバーが500(Internal Server Error)のステータスで応答した【非公開
-
[解決済み】React - TypeError: 未定義のプロパティ 'props' を読み取ることができない。
-
[解決済み】 Uncaught TypeError : undefined のプロパティ 'replace' を読み取れない In Grid
-
Uncaught TypeError: null のプロパティ 'offsetHeight' を読み取れませんでした。
-
[解決済み] Reduxの非同期フローになぜミドルウェアが必要なのか?