一切优秀的框架都是为程序员服务的:reactjs提供更优雅的前端代码书写方式和更优的界面更新机制,react-native提供一种性能更优的混合开发方案,redux提供了组件和业务分离的解决方案,thunk或saga基于redux提供异步业务实现方案...
这篇文章只讲redux-saga的使用。先看一张图:
图中的Middleware工作于redux内部,介于action和reducer之间,而saga只是某一种Middleware。要想使用它,首先得知道redux具体是如何运用的:
Redux的使用步骤
-
首先定义所需组件
组件的功能很简单:只有一个文本和一个按钮,点击按钮将文本中的数字加1
另外,组件的props需要有count、data
两个属性,前者是个函数(按钮的点击事件中会执行它),后者是个对象(文本的内容取自它),这是它向上层(这里可以指它的父组件或者redux)传达的需求
class SagaTestComp extends React.Component {
_onCount = () => {
//将当前count传给action watcher saga监听到这个action后 会将action传递给worker saga
const curCount = this.props.data.get('count')
this.props.count(curCount)
}
render() {
console.log('SagaTestComp rendered')
return (
{this.props.data.get('count')}
)
}
}
-
声明两种映射关系
redux提供了映射组件的props中的对象属性和函数属性的方法
//映射组件props的数据部分
const mapStateToProps = (state) => {
return {
//data将作为所绑定的Component的props的一部分, 它的值取自redux的state对象的SagaTest属性
data: state.SagaTest
}
}
//映射组件props的函数部分
const mapDispatchToProps = (dispatch) => {
return {
//函数count将作为所绑定Component的props的一部分,函数体中执行了redux的dispatch方法
count: (param) => {
return dispatch(getCountAction(param))
}
}
}
-
连接组件
本质就是向redux注册组件,这样redux才知道组件的props需要哪些内容
const Content = connect(
mapStateToProps,
mapDispatchToProps
)(SagaTestComp)
-
定义会触发组件更新的事件
这些事件叫action,由组件触发;每种action由type属性唯一标识,不可重复
//计数
const getCountAction = (param) => {
return {
type: 'count',
param
}
}
//计数完成
const getCountFinishAction = (param) => {
return {
type: 'countFinish',
param
}
}
-
定义修改redux中state的纯函数
这些纯函数叫reducer,每个reducer可接收两个参数:state、action
;前者是这个reducer负责管辖的数据(改变它会引起redux中相应state的变化,从而引起组件更新),后者表示前面定义的诸多action中的某一个。每个reducer可通过action的type属性对指定的事件做出响应。
//初始数据 这里使用Immutable数据
const initData = {
SagaTest: Immutable.fromJS({
count: 0
})
}
//reducer 纯函数 触发component的更新
const reducer = (state = initData, action) => {
switch (action.type) {
case 'countFinish':
console.log(`reducer receive the action ${action.type}`)
//因为redux的state是不可变数据 所以这里必须返回一个新对象才会触发component的更新
return {
SagaTest: state.SagaTest.update('count', () => action.param)//这里将计数更新成counter计算的结果
}
default:
return state
}
}
Redux-Saga的使用
前面讲了,saga是Middleware的一种,工作于action和reducer之间。如果按照原始的redux工作流程,当组件中产生一个action后会直接触发reducer修改state;而往往实际中,组件中发生的action后,在进入reducer之前需要完成一个异步任务,显然原生的redux是不支持这种操作的。
不过,实现异步操作的整体步骤是很清晰的:action被触发后,首先执行异步任务,待完成后再将这个action交给reducer。说到这里,thunk和saga的达到的效果是一致的,差别在于对action的处理完全不一样。
thunk采用的是扩展action的方式:使得redux的store能dispatch的内容从普通对象扩展到函数
mapDispatchToProps = (dispatch) => {
return {
delayBanner: () => dispatch(delayBanner)
}
}
const delayBannerAction = {
type: DELAY_BANNER
}
//至于什么时候真正执行dispatch方法 取决于业务本身
export const delayBanner = (dispatch) => {
delayBannerTask(dispatch, delayBannerAction)
}
saga采用的方案更接近于redux的全局思想,使用方式和thunk有很大不同:
saga需要一个全局监听器(watcher saga),用于监听组件发出的action,将监听到的action转发给对应的接收器(worker saga),再由接收器执行具体任务,任务执行完后,再发出另一个action交由reducer修改state,所以这里必须注意:watcher saga监听的action和对应worker saga中发出的action不能是同一个,否则造成死循环
在saga中,全局监听器和接收器都使用Generator函数和saga自身的一些辅助函数实现对整个流程的管控
整个流程可以简单描述为
Component —> Action1 —> Watcher Saga —> Worker Saga —> Action2 —> Reducer —> Component
相比thunk,saga是多了一个action的,因为saga是将业务的触发(watcher saga)和业务的执行(worker saga)分开描述的
const getCountAction = (param) => {
return {
type: 'count',
param
}
}
const getCountFinishAction = (param) => {
return {
type: 'countFinish',
param
}
}
const mapDispatchToProps = (dispatch) => {
return {
count: (param) => {
return dispatch(getCountAction(param))//这里dispatch的参数是个普通对象
}
}
}
//一个普通函数 将当前计数+1
function counter(curCount) {
return ++curCount
}
//worker saga 这里其实是有接收参数的 这个参数action就是redux执行dispatch方法中的参数
function* counterSaga(action) {
console.log(`counterSaga receive the action ${action.type}`)
//接受到这个action之后 执行saga的call方法 call方法会执行counter函数 并将当前计数作为它的参数
const countResult = yield call(counter, action.param)
//counter执行的结果作为另一个action的参数
yield put(getCountFinishAction(countResult))
}
//watcher saga 监听takeEvery这个action 并执行helloSaga
function* watchIncrementAsync() {
console.log('watcher saga is listening to action')
yield* takeEvery('count', counterSaga);
}
//执行watcher saga 这样就可以一直监听应用中的action了
sagaMiddleware.run(watchIncrementAsync)