Redux-Saga.js 学习笔记
昨天认真看完了Promise 与 Generator,今天早上认真学了一下redux-saga.js。把笔记输出到这里
redux-saga的api
-
put
作用类似于 dispatch,用来 put 同步 的 generator function,也把一些状态 put 到 reducer
-
call
我的理解是 调用api , 发起请求,也可以用来call 同步的 action
官方说明
与前面的例子不同的是,现在我们不立即执行异步调用,相反,
call
创建了一条描述结果的信息。 就像在 Redux 里你使用 action 创建器,创建一个将被 Store 执行的、描述 action 的纯文本对象。call
创建一个纯文本对象描述函数调用。redux-saga
middleware 确保执行函数调用并在响应被 resolve 时恢复 generator。需要注意的,写异步流的时候
// 错误写法,effects 将按照顺序执行 const users = yield call(fetch, '/users'), repos = yield call(fetch, '/repos') // 由于第二个 effect 将会在第一个 call 执行完毕才开始。所以我们需要这样写: import { call } from 'redux-saga/effects' // 正确写法, effects 将会同步执行 const [users, repos] = yield [ call(fetch, '/users'), call(fetch, '/repos') ]
-
takeEvery
非常常见,提供了类似 redux-thunk的行为
// 首先我们创建一个将执行异步 action 的任务: import { call, put } from 'redux-saga/effects' export function* fetchData(action) { try { const data = yield call(Api.fetchUser, action.payload.url); yield put({type: "FETCH_SUCCEEDED", data}); } catch (error) { yield put({type: "FETCH_FAILED", error}); } } // 然后在每次 FETCH_REQUESTED action 被发起时启动上面的任务。 import { takeEvery } from 'redux-saga' function* watchFetchData() { yield* takeEvery('FETCH_REQUESTED', fetchData) }
在上面的例子中,
takeEvery
允许多个fetchData
实例同时启动。在某个特定时刻,尽管之前还有一个或多个fetchData
尚未结束,我们还是可以启动一个新的fetchData
任务, -
takeLatest
如果我们只想得到最新那个请求的响应(例如,始终显示最新版本的数据)。我们可以使用
takeLatest
辅助函数。import { takeLatest } from 'redux-saga' function* watchFetchData() { yield* takeLatest('FETCH_REQUESTED', fetchData) }
和
takeEvery
不同,在任何时刻takeLatest
只允许一个fetchData
任务在执行。并且这个任务是最后被启动的那个。 如果已经有一个任务在执行的时候启动另一个fetchData
,那之前的这个任务会被自动取消。 -
select
根据 在组件中dispatch一个action的例子中,如果要在effects中对于param数据和当前的state数据进行再出处理,这里怎么获取state呢?采用select,如下:
export default { namespace: 'example', state: {num:1}, //表示当前的example中的state状态,这里可以给初始值,这里num初始为1 effects: { //这里是做异步处理的 *addByONe({ param}, { call, put,select }) { //这里使用select const num = yield select(state => state.num) //这里就获取到了当前state中的数据num //方式二: const num = yield select(({num}) =>num) //方式三: const num = yield select(_ =>_.num) let param1; param1 = num + param; 这里就可以使用num进行操作了 yield put({ type: 'save', num:param1 }); } }, //用来保存更新state值 上面的put方法调用这里的方法 reducers: { save(state, action) { //这里的state是当前总的state,这里的action包含了上面传递的参数和type return { ...state, ...action.num }; //这里用ES6语法来更新当前state中num的值 }, }, };
-
take
take
就像我们更早之前看到的call
和put
。它创建另一个命令对象,告诉 middleware 等待一个特定的 action。 正如在call
Effect 的情况中,middleware 会暂停 Generator,直到返回的 Promise 被 resolve。 在take
的情况中,它将会暂停 Generator 直到一个匹配的 action 被发起了。 在以上的例子中,watchAndLog
处于暂停状态,直到任意的一个 action 被发起。import { select, take } from 'redux-saga/effects' function* watchAndLog() { while (true) { const action = yield take('*') const state = yield select() console.log('action', action) console.log('state after', state) } }
一个简单的例子,假设在我们的 Todo 应用中,我们希望监听用户的操作,并在用户初次创建完三条 Todo 信息时显示祝贺信息。
import { take, put } from 'redux-saga/effects' function* watchFirstThreeTodosCreation() { for (let i = 0; i < 3; i++) { const action = yield take('TODO_CREATED') } yield put({type: 'SHOW_CONGRATULATION'}) }
使用拉取(pull)模式,我们可以在同一个地方写控制流,而不是重复处理相同的 action。
function* loginFlow() { while (true) { yield take('LOGIN') // ... perform the login logic yield take('LOGOUT') // ... perform the logout logic } }
-
fork
为了表示无阻塞调用,redux-saga 提供了另一个 Effect:
fork
。 当我们 fork 一个 任务,任务会在后台启动,调用者也可以继续它自己的流程,而不用等待被 fork 的任务结束。所以为了让
loginFlow
不错过一个并发的LOGOUT
,我们不应该使用call
调用authorize
任务,而应该使用fork
。import { take, call, put, cancelled } from 'redux-saga/effects' import Api from '...' function* authorize(user, password) { try { const token = yield call(Api.authorize, user, password) yield put({type: 'LOGIN_SUCCESS', token}) yield call(Api.storeItem, {token}) return token } catch(error) { yield put({type: 'LOGIN_ERROR', error}) } finally { if (yield cancelled()) { // ... put special cancellation handling code here } } }
-
cancel
如果我们收到一个
LOGOUT
action,我们将那个 task 传入给cancel
Effect。 如果任务仍在运行,它会被中止。如果任务已完成,那什么也不会发生,取消操作将会是一个空操作(no-op)。最后,如果该任务完成了但是有错误, 那我们什么也没做,因为我们知道,任务已经完成了。import { take, call, put, cancelled } from 'redux-saga/effects' import Api from '...' function* authorize(user, password) { try { const token = yield call(Api.authorize, user, password) yield put({type: 'LOGIN_SUCCESS', token}) yield call(Api.storeItem, {token}) return token } catch(error) { yield put({type: 'LOGIN_ERROR', error}) } finally { if (yield cancelled()) { // ... put special cancellation handling code here } } }
-
race
有时候我们同时启动多个任务,但又不想等待所有任务完成,我们只希望拿到 胜利者:即第一个被 resolve(或 reject)的任务。
race
Effect 提供了一个方法,在多个 Effects 之间触发一个竞赛(race)。// 下面的示例演示了触发一个远程的获取请求,并且限制了 1 秒内响应,否则作超时处理。 import { race, call, put } from 'redux-saga/effects' import { delay } from 'redux-saga' function* fetchPostsWithTimeout() { const {posts, timeout} = yield race({ posts: call(fetchApi, '/posts'), timeout: call(delay, 1000) }) if (posts) put({type: 'POSTS_RECEIVED', posts}) else put({type: 'TIMEOUT_ERROR'}) }
race
的另一个有用的功能是,它会自动取消那些失败的 Effects。例如,假设我们有 2 个 UI 按钮:- 第一个用于在后台启动一个任务,这个任务运行在一个无限循环的
while(true)
中(例如:每 x 秒钟从服务器上同步一些数据) - 一旦该后台任务启动了,我们启用第二个按钮,这个按钮用于取消该任务。
import { race, take, call } from 'redux-saga/effects' function* backgroundTask() { while (true) { ... } } function* watchStartBackgroundTask() { while (true) { yield take('START_BACKGROUND_TASK') yield race({ task: call(backgroundTask), cancel: take('CANCEL_TASK') }) } }
- 第一个用于在后台启动一个任务,这个任务运行在一个无限循环的