前言
本文主要记录了在项目中使用redux-saga的一些总结,如有错误的地方欢迎指正互相学习。
redux中的action仅支持原始对象(plain object),处理有副作用的action,需要使用中间件。中间件可以在发出action,到reducer函数接受action之间,执行具有副作用的操作。
redux-thunk 和 redux-saga 是 redux 应用中最常用的两种异步流处理方式。
之前一直使用redux-thunk处理异步等副作用操作,在action中处理异步等副作用操作,此时的action是一个函数,以dispatch,getState作为形参,函数体内的部分可以执行异步。通过redux-thunk来处理异步,action可谓是多种多样,不利于
redux-thunk
redux-thunk简单介绍
redux-thunk
的任务执行方式是从 UI 组件直接触发任务。
redux-thunk
中间件可以让action创建函数先不返回一个action对象,而是返回一个函数,函数传递两个参数(dispatch,getState),在函数体内进行业务逻辑的封装
redux-thunk 的主要思想是扩展 action,使得 action 从一个对象变成一个函数。
redux-thunk使用
比如下面是一个获取礼品列表的异步操作所对应的action
-
export default () => dispatch => {
-
fetch('/api/goodList', {
-
// fecth返回的是一个promise
-
method: 'get', dataType: 'json' }).then(
-
json => {
-
var json = JSON.parse(json)
-
if (json.code === 200) {
-
dispatch({ type: 'init', data: json.data })
-
}
-
}, error => { console.log(error) }
-
)
-
}
从这个具有副作用的action中,我们可以看出,函数内部极为复杂。如果需要为每一个异步操作都如此定义一个action,显然action不易维护。
redux-thunk
缺点
总结一下redux-thunk
缺点有如下几点:
-
action 虽然扩展了,但因此变得复杂,后期可维护性降低;
-
thunks 内部测试逻辑比较困难,需要mock所有的触发函数;
-
协调并发任务比较困难,当自己的 action 调用了别人的 action,别人的 action 发生改动,则需要自己主动修改;
-
业务逻辑会散布在不同的地方:启动的模块,组件以及thunks内部。
redux-saga
redux-saga简单介绍
redux-saga
文档中是这样介绍的:
redux-saga 是一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的 library,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易。
刚开始了解Saga时,看官方解释,并不是很清楚到底是什么?Saga的副作用(side effects)到底是什么?
通读了官方文档后,大概了解到,副作用就是在action触发reduser之后执行的一些动作, 这些动作包括但不限于,连接网络,io读写,触发其他action。并且,因为Sage的副作用是通过redux的action触发的,每一个action,sage都会像reduser一样接收到。并且通过触发不同的action, 我们可以控制这些副作用的状态, 例如,启动,停止,取消。
所以,我们可以理解为Sage是一个可以用来处理复杂的异步逻辑的模块,并且由redux的action触发。
saga特点:
-
1.saga的应用场景是复杂异步,如长时事务LLT(long live.transcation)等业务场景。
-
2.方便测试,可以使用takeEvery打印logger。
-
3.提供takeLatest/takeEvery/throttle方法,可以便利的实现对事件的仅关注最近事件、关注每一次、事件限频
-
4.提供cancel/delay方法,可以便利的取消、延迟异步请求
-
5.提供race(effects),[…effects]方法来支持竞态和并行场景
-
6.提供channel机制支持外部事件
Redux Saga适用于对事件操作有细粒度需求的场景,同时他们也提供了更好的可测试性。
redux-saga使用
注意:⚠️redux-saga是通过ES6中的generator实现的(babel的基础版本不包含generator语法,因此需要在使用saga的地方import ‘babel-polyfill’)。
redux-saga本质是一个可以自执行的generator。
在 redux-saga 中,UI 组件自身从来不会触发任务,它们总是会 dispatch 一个 action 来通知在 UI 中哪些地方发生了改变,而不需要对 action 进行修改。redux-saga 将异步任务进行了集中处理,且方便测试。
所有的东西都必须被封装在 sagas 中。sagas 包含3个部分,用于联合执行任务:
worker saga
(1)做所有的工作,如调用 API,进行异步请求,并且获得返回结果
watcher saga
(2)监听被 dispatch 的 actions,当接收到 action 或者知道其被触发时,调用 worker saga 执行任务
(3)root saga
立即启动 sagas 的唯一入口
项目中我是这样用的,如果你有更好的实现方法请分享给我:
给redux添加中间件
在定义生成store的地方,引入并加入redux-sage中间件。
-
// store/index.js
-
import { createStore, applyMiddleware, compose } from 'redux'
-
import { routerMiddleware } from 'react-router-redux'
-
import createSagaMiddleware from 'redux-saga'
-
import createHistory from 'history/createHashHistory'
-
import { createLogger } from 'redux-logger'
-
import { rootSaga } from '../rootSaga'
-
import reducers from '../reducers/saga-reducer'
-
const history = createHistory()
-
const middlewareRouter = routerMiddleware(history)
-
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
-
const loggerMiddleware = createLogger({ collapsed: true })
-
// 这是一个可以帮你运行saga的中间件
-
const sagaMiddleware = createSagaMiddleware()
-
const store = createStore(reducers,
-
composeEnhancers(
-
applyMiddleware(
-
sagaMiddleware, middlewareRouter, loggerMiddleware
-
)))
-
// 通过中间件执行或者说运行saga
-
sagaMiddleware.run(rootSaga, store)
-
window.store = store
-
export default store
说明:程序启动时,run(rootSaga) 会开启 sagaMiddleware 对某些 action 进行监听,当后续程序中有触发 dispatch(action) (比如:用户点击)的时候,由于数据流会经过 sagaMiddleware,所以 sagaMiddleware 能够判断当前 action 是否有被监听?如果有,就会进行相应的操作(比如:发送一个异步请求);如果没有,则什么都不做。
-
// rootSaga.js
-
// 处理浏览器兼容问题
-
import 'babel-polyfill'
-
import { all,call } from 'redux-saga/effects'
-
import { lotterySagaRoot } from './components'
-
import { getchampionListFlow, getTabsListFlow } from './container'
-
export function* rootSaga () {
-
yield all([call(getTabsListFlow),
-
call(getchampionListFlow),
-
call(lotterySagaRoot),
-
])
-
}
rootSaga
是我们实际发送给Redux中间件的。
rootSaga
在应用程序启动时被触发一次,可以被认为是在后台运行的进程,监视着所有的动作派发到仓库(store)。
我们单拿出一个 getTabsListFlow 这个saga来进行讲解究竟发生了什么?
写到这里有必要说一下业务逻辑了,getTabsListFlow这个函数是一个watcher saga
,它 watch 的谁呢?getTabsList
这个worker saga
函数,废话不多说看代码:
-
// 处理浏览器兼容问题
-
import 'babel-polyfill'
-
import { call, put, take, fork } from 'redux-saga/effects'
-
import * as types from '../../action_type'
-
import { lists } from '../../actions/server'
-
const { GETLIST, TABS_UPDATE, START_FETCH, FETCH_ERROR, FETCH_END } = types
-
//----worker