有关于异步处理, 通用解决方案有这些: redux-thunk,redux-promise,redux-saga
最后为什么选择了 redux-saga, 因为 thunk 和 promise 都有的问题是: 他们改变了 action 的含义, 使得 action 变得不那么纯粹了.
redux-saga 是一个用于管理应用程序 Side Effect(副作用, 例如异步获取数据, 访问浏览器缓存等)的 library, 它的目标是让副作用管理更容易, 执行更高效, 测试更简单, 在处理故障时更容易.
Effect 被称为副作用, 在我们的应用中, 最常见的就是异步操作. 它来自于函数编程的概念, 之所以叫副作用是因为它使得我们的函数变得不纯, 同样的输入不一定获得同样的输出.
redux-saga 是一个 redux 中间件, 意味着这个线程可以通过正常的 redux action 从主应用程序启动, 暂停和取消, 它能访问完整的 redux state, 也可以 dispatch redux action.
启动运行 Sagas:
- import { createStore, , applyMiddleware } from 'redux'
- import createSagaMiddleware from 'redux-saga'
- import reducer from './reducers'
- // 要运行的 Sagas
- import sagas from './sagas'
- // 创建一个 Saga middleware
- const sagaMiddleware = createSagaMiddleware()
- const store = createStore(
- reducer,
- // 将 middleware 连接至 Store
- applyMiddleware(sagaMiddleware)
- )
- // 运行 Sagas
- sagaMiddleware.run(sagas)
- // 执行
- store.dispatch(action)
applyMiddleware(...middleware)是一种推荐用来扩展 Redux 的方式, 告诉 createStore() 如何处理中间件, 让你包装 store 的 dispatch 方法来达到你想要的目的.
创建一个 Saga:
saga 被实现为 Generator 函数 (我梳理的 Generator 函数), 让异步的流程更易于读取, 写入和测试. 通过这样的方式, 这些异步的流程看起来就像是标准同步的 JavaScript 代码.
- // 只是单纯的创建了 saga, 打印了一条消息, 没有进行异步调用
- function* helloSaga() {
- console.log('Hello Sagas!');
- }
Sagas 的基本使用:
Effect 是一个简单的对象, 这个对象包含了一些给 middleware 解释执行的信息, 可以把 Effect 看作是发送给 middleware 的指令以执行某些操作(调用某些异步函数, 发起一个 action 到 store, 等等).
Effect 创建器: 创建一个 Effect 描述信息, 只返回一个普通 JavaScript 对象, 并不会执行任何操作. 执行是由 middleware 进行的, middleware 会检查每个 Effect 的描述信息, 并进行相应的操作.
Saga 辅助函数: 是构建在 Effect 创建器之上的辅助函数.(即高级 API)
执行 dispatch:put(action)
创建一个 Effect 描述信息, 用来命令 middleware 向 Store 发起一个 action( 这个 effect 是非阻塞型的 ).
- import { put } from 'redux-saga/effects'
- function* example() {
- yield put({ type: 'CHESTNUT' })
- }
阻塞与非阻塞形式调用: call(fn, ...args),fork(fn, ...args)
fork 和 call 共同点:
都是创建一个 Effect 描述信息, 用来命令 middleware 以参数 args 调用函数 fn(fn 可以是一个普通函数, 也可以是一个 Generator 函数)
fork 和 call 区别点:
call: 是一个会阻塞的 Effect, 即 Generator 在调用结束之前不能执行或处理任何其他事情.
fork: 非阻塞调用 的形式执行 fn(Generator 不会在等待 fn 返回结果的时候被 middleware 暂停, 它在 fn 被调用时便会立即恢复执行)
- import { take, put, fork ,call } from 'redux-saga/effects'
- // 一个工具函数 delay, 用于阻塞执行 ms 毫秒, 并返回 val 值.
- //delay(ms, [val])
- import { delay } from 'redux-saga'
- function* example1() {
- yield call({delay , 1000})
- // 过 1 秒后打印出'print call'
- console.log('print call')
- }
- function* chestnut() {
- yield fork({delay , 1000})
- // 直接打印出'print fork'
- console.log('print fork')
- }
监听 action:take(pattern)
创建另一个命令对象, 告诉 middleware 等待一个指定的 action.
在 take 的情况中, 与 action 被推向任务处理函数不同, Saga 是自己主动拉取 action 的.
- import { select, take } from 'redux-saga/effects'
- // 用户执行 3 次 CHESTNUT 后才会执行 CHESTNUT_ONE, 然后 Generator 会被回收并且相应的监听不会再发生
- function* chestnutOne() {
- for (let i = 0; i <3; i++) {
- yield take('CHESTNUT_CREATION')
- }
- yield put({type: 'CHESTNUT_SUCCEED'})
- }
- function* chestnutTwo() {
- // 用户监听 2 个并发的 action, 如果用户执行了 CHESTNUT_ONE 或 CHESTNUT_TWO, 则会执行 CHESTNUT_ONE
- while (true) {
- yield take(['CHESTNUT_ONE', 'CHESTNUT_TWO'])
- yield put({type: 'CHESTNUT_NUM'})
- }
- }
sagas 辅助函数: takeEvery(pattern, saga, ...args) ,takeLatest(pattern, saga, ...args)
sagas 辅助函数是使用 take 和 fork 构建的高级 API
takeEvery(): 会存在 多个 saga 任务
- import { fork , take } from 'redux-saga/effects'
- import { takeEvery } from 'redux-saga'
- function* onesaga(action) {
- ...
- }
- function* chestnutOne() {
- yield* takeEvery('CHESTNUT_REQUESTED', onesaga)
- }
- //takeEvery 用低阶 Effects 的实现
- const takeEvery = (pattern, saga, ...args) => fork(function*() {
- while (true) {
- const action = yield take(pattern)
- yield fork(saga, ...args.concat(action))
- }
- })
takeLatest(): 只会存在 一个 saga 任务, 会自动取消之前已经启动但仍在执行中的 saga 任务.
- import { fork , take ,cancel } from 'redux-saga/effects'
- import { takeLatest } from 'redux-saga'
- function* onesaga(action) {
- ...
- }
- function* chestnutTwo() {
- yield* takeLatest('CHESTNUT_REQUESTED', onesaga)
- }
- //takeLatest 用低阶 Effects 的实现
- const takeLatest = (pattern, saga, ...args) => fork(function*() {
- let lastTask
- while (true) {
- const action = yield take(pattern)
- if (lastTask) {
- yield cancel(lastTask) // 如果任务已经结束, 则 cancel 为空操作
- }
- lastTask = yield fork(saga, ...args.concat(action))
- }
- })
关于同时启动多个任务:
同时启动多个任务, 等待所有的 effects 都执行完毕, 或者当一个 effect 被拒绝 (就像 Promise.all 的行为)
- const [one, two] = yield [
- call(fetch, '/one'),
- call(fetch, '/two')
- ]
同时启动多个任务, 不等待所有 effects 都执行完毕. 在多个 Effect 间运行 竞赛 (Race)(与 Promise.race([...]) 的行为类似), 只拿第一个被 resolve(或 reject) 的任务.(注意: race 它会自动取消那些失败的 Effects)
- import { race } from 'redux-saga/effects'
- // 在请求返回之前, 可触发 CANCEL_FETCH action 取消该请求
- function* chestnut{
- const { response, cancel } = yield race({
- response: call(fetch),
- cancel: take('CANCEL_FETCH')
- })
- }
取消任务: cancel(task)
一旦任务被 fork, 可以使用 yield cancel(task) 来中止任务执行, 取消正在运行的任务.
- import { take, fork, cancel } from 'redux-saga/effects'
- function* chestnut() {
- while(true) {
- const task = yield fork(fetch)
- yield take('STOP')
- yield cancel(task)
- }
- }
最后整合 sagas:
- import {all} from 'redux-saga/effects'
- // 一个入口点, 立即启动所有的 Sagas
- export default function* sagas() {
- yield all([
- example(),
- example1(),
- // more sagas
- ])
- }
更多具体详细查看 API 文档
现在 redux 项目中的一些问题是:
概念太多, 并且 reducer, saga, action 都是分离的, 需要在 reducer, saga, action 之间来回切换, 编辑成本高.
在真实的项目应用 redux 中, 除了 redux store 的创建, 中间件的配置, 路由的初始化, Provider 的 store 的绑定, saga 的初始化, 还要处理 reducer, component, saga 的 HMR(hot module replacement 模块热替换) , 看起来比较复杂.
......
有没有更好的简化方案呢? React + Redux 最佳实践实现的 framework--dva(待续)
来源: http://www.jianshu.com/p/a4937ac989a3