Redux-saga实践总结及其高级功能在DvaJS中的应用
选择 dvajs
最近一直忙于公司产品的前端重构,因为之前学习和接触的东西一直以后端为主,使用的语言又是以 PHP 为主,所以对于前端开发使用的语言 ES6 的代码规范和编程风格不太熟悉,对于前端工程化的经验也是几乎为零。虽然 leader 给了充足的学习时间和强大的技术支持,但写代码的就我自己。于是乎我就选择了 antd+dvajs,一来框架作者提供的 Demo 可以让我去学习前端的设计思想和代码的编写规范,二来我不需要耗费过多的精力去搭建整个项目的生态。站在巨人的肩上,才能更好更快地拥抱 react/redux 全家桶。
redux-saga
什么是 saga
Saga这个名词常被用在CQRS的讨论中,它是指一段在限定上下文(bounded contexts)和聚合(aggregates)之间起协作和路由(coordinates and routes)消息作用的代码。最早被定义在Hector Garcia-Molina和Kenneth Salem的论文”Sagas“中。这篇论文提出了一个saga机制来作为分布式事务的替代品以解决长时间运行的分布式事务(long-running process)的问题。这篇论文认为业务过程经常由很多步骤组成,每个步骤都涉及一个事务,如果将这些事务组成一个分布式事务,就可以实现总体一致(overall consistency)。
redux-saga 中几个抽象的概念:
Effect
Effect 是一个 javascript 对象,通过使用 redux-saga/effects 包里提供的函数来创建,里面包含描述异步操作(Side Effect)的信息,通过 yield 传达给 sagaMiddleware 执行, 我们可以把每一个 Effect 看成一道发送给 middleware 的指令。
Saga
所有的 Saga 都是由 Generator 函数实现,所做的实际上是通过组合 Effect,以实现所需的控制流。最简单的例子就是把 yield Effects 一个接一个地放置,就可以让 Effect 按顺序执行。
Task
一个 task 就像是一个在后台运行的进程。在基于 redux-saga 的应用程序中,可以同时运行多个 task。通过 fork 函数来创建 task,同时可以使用 cancel 函数来结束 task。
原生 redux-saga 的使用
鉴于文档中有详细的解释,这里只做介绍,查看文档建议阅读官方英文文档,中文文档一是没有及时更新,导致一些内容在中文文档里没有,另外翻译的水平也不太行,有些地方甚至出现错误的翻译。
Effect 创建器
put
作用和 redux 中的 dispatch 相同。
yield put({ type: 'CLICK_BTN' });
select
作用和 redux thunk 中的 getState 相同。
const id = yield select(state => state.id);
- Ps:saga 最好是自主独立的,不应依赖 Store 的 state。这使得很容易修改 state 实现部分而不影响 Saga 代码。
take
监听(pulling)一个能匹配指定 pattern 的 action。
1 | |
一个简单的日志记录器的例子:
1 | |
call
阻塞地调用 saga 或者返回 promise 的函数。
yield call(method, arg1, arg2, ...);
同样支持调用对象方法,可以用下面这种方式,为调用的函数提供一个 this 上下文:
yield call([obj, obj.method], arg1, arg2, ...) // 如同 obj.method(arg1, arg2 ...)
fork
无阻塞地调用 saga 或者返回 promise 的函数, 返回一个 Task 对象。
yield fork(authorize, username, password)
cancel
取消之前 fork 的 task。
下面的例子实现了在登陆过程中,一旦收到LOGOUT ,马上终止authorize ,然后在任务内部捕获取消错误,并执行内部的清理逻辑。
1 | |
Ps-1:在取消任务中出现嵌套的 saga 时,所有的 saga 都会被取消,即取消会向下传播,但未被捕获的错误不会向上冒泡。
Ps-2:取消任务时,saga 中的
try...finally代码块,finally里的代码会照常执行。Ps-3:
yield cancel(task)是非阻塞的,因此任务一旦取消,就应当尽快完成其清理逻辑。
Effect 辅助函数
takeEvery
监听(pulling)每个能匹配指定 pattern 的 action。
1 | |
takeLatest
监听(pulling)最后一个能匹配指定 pattern 的 action,之前启动的任务如果正在执行中,则会被自动取消。
1 | |
throttle
节流器。监听到一个匹配的 action 后,忽略一段时间内匹配到的其他 action,但是保留这段时间中最后一个匹配到的 action。yield throttle(1000, 'FETCH_AUTOCOMPLETE', fetchAutocomplete)
Effect 组合器(combinators)
race
在多个 Effect 之间执行一个 race(类似 Promise.race([...]) 的行为)。
下面的示例演示了触发一个远程的获取请求,并且限制了 1 秒内响应,否则作超时处理。
1 | |
[...effects]
并行执行多个 Effect,然后等待所有 Effect 完成(类似 Promise.all([...]) 的行为)。
下面的实例并行执行了 2 个阻塞调用:
1 | |
redux-saga 高级功能在 dvajs 中的应用
在 dvajs 中,作者选择了 redux-saga 处理异步操作。而对于异步处理的问题,目前比较流行的通用解决方案如下:
前两个方案一个是支持函数形式的action,另一个是支持 promise 形式的 action,但是有一个共同的问题就是:都改变了action的含义,使得action的含义不那么纯粹了。
redux-saga则以优雅而强大的方式,把业务逻辑都放在saga中,通过监听 action 来执行有副作用的 task,并且引入了 Sagas 的机制和 generator 的特性,让 reducer, action 和 component 都很纯粹地干他们原本需要干的事情。
但是,在目前的 dvajs 中的 effects 默认是 takeEvery (dva@1.2.1),虽然文档中没有关于其他类型 effects 的说明,但在 源码 中我们可以看到,作者其实是提供了使用方法的。
通过下面的方式:
1 | |
就可以使用所有原生 redux-saga 中的控制方式了。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!