redux-saga GitHub地址
https://github.com/yelouafi/redux-saga
redux-saga 官方文档
文档地址
redux-saga 简介
一种专门用来处理异步请求(例如:fetch)的redux插件
使用方式
npm install --save redux-saga
当然也支持UMD的加载方式
https://unpkg.com/redux-saga/dist/redux-saga.js
https://unpkg.com/redux-saga/dist/redux-saga.min.js
以前的方法有什么缺点
通过redux-thunk使用action创造器做一些流程控制的事情。这样可能会很难测试,但是管用。
// an action creator with thunking
function createRequest () {
return function (dispatch, getState) {
dispatch({ type: 'REQUEST_STUFF' })
someApiCall(function(response) {
// some processing
dispatch({ type: 'RECEIVE_STUFF' })
}
}
}
可能在组件中还会写到
function onHandlePress () {
this.props.dispatch({ type: 'SHOW_WAITING_MODAL' })
this.props.dispatch(createRequest())
}
使用redux状态树(和reducers)来扮演一个信号系统,把东西链接在一起。 到处都是代码
Saga长什么样子?
Saga其实是generator生成器函数
function *hello() {
}
对thunk的改进
function *hello() {
yield take('BEGIN_REQUEST')
yield put({ type: 'SHOW_WAITING_MODAL' })
const response = yield call(myApiFunctionThatWrapsFetch)
yield put({ type: 'PRELOAD_IMAGES', response.images })
yield put({ type: 'USAGE_TRACK', activity: 'API_CALL'})
yield put({ type: 'HIDE_WAITING_MODAL' })
}
示例
[图片上传失败...(image-d8de54-1527745550799)]
如果简单的用react-redux实现,缺点是:组件视图里面会混入一些应用逻辑代码
class Timer extends Component {
componentWillReceiveProps(nextProps) {
const { state: { status: currStatus } } = this.props;
const { state: { status: nextStatus } } = nextProps;
if (currState === 'Stopped' && nextState === 'Running') {
this._startTimer();
} else if (currState === 'Running' && nextState === 'Stopped') {
this._stopTimer();
}
}
_startTimer() {
this._intervalId = setInterval(() => {
this.props.tick();
}, 1000);
}
_stopTimer() {
clearInterval(this._intervalId);
}
// ...
}
如果采用redux-thunk,我们可以这样写,缺点是:简单地将应用逻辑引入到了action中,导致action过于复杂,并且不利于测试
export default {
start: () => (
(dispatch, getState) => {
// This transitions state to Running
dispatch({ type: 'START' });
// Check every 1 second if we are still Running.
// If so, then dispatch a `TICK`, otherwise stop
// the timer.
const intervalId = setInterval(() => {
const { status } = getState();
if (status === 'Running') {
dispatch({ type: 'TICK' });
} else {
clearInterval(intervalId);
}
}, 1000);
}
)
// ...
};
采用redux-saga,就可以完美地单独构建应用逻辑
function* runTimer(getState) {
// The sagasMiddleware will start running this generator.
// Wake up when user starts timer.
while(yield take('START')) {
while(true) {
// This side effect is not run yet, so it can be treated
// as data, making it easier to test if needed.
yield call(wait, ONE_SECOND);
// Check if the timer is still running.
// If so, then dispatch a TICK.
if (getState().status === 'Running') {
yield put(actions.tick());
// Otherwise, go idle until user starts the timer again.
} else {
break;
}
}
}
}
示例源码地址
推荐文章
常用API
初始化API
Middleware API
createSagaMiddleware(options)
用于创建一个中间件并连接saga和redux。
middleware.run(saga, ...args)
启动saga,此方法能自动检测yield的执行,当前yield返回时,会执行next(result),result是上一个yield返回的结果。一般saga的Generator函数会写成try/catch的形式,当执行出错是就会执行catch。如果saga在中途被cancel掉了,就会执行Generator里的return。
上层API
takeEvery(pattern, saga, ...args)
相当于redux-thunk的执行模式,响应匹配到的状态多少次,执行多少次,但是不会根据响应的次序返回response。
takeLatest(pattern, saga, ...args)
如果上一次异步响应还没有返回,此时又接收到一次匹配的状态,则会cancel掉前一次的请求,去执行最新的一次请求。
##### 底层API
take(pattern)
相对于takeEvery,更底层,但是更实用,实例代码如下:
function* watchAndLog() {
yield takeEvery('*', function* logger(action) {
const state = yield select()
console.log('action', action)
console.log('state after', state)
})
}
function* loginFlow() {
while (true) {
yield take('LOGIN')
// ... perform the login logic
yield take('LOGOUT')
// ... perform the logout logic
}
}
使用Channels
import { take, fork, ... } from 'redux-saga/effects'
function* watchRequests() {
while (true) {
const {payload} = yield take('REQUEST')
yield fork(handleRequest, payload)
}
}
function* handleRequest(payload) { ... }
actionChannel
actionChannel用来接收redux store内部状态消息
import { take, actionChannel, call, ... } from 'redux-saga/effects'
function* watchRequests() {
// 1- Create a channel for request actions
const requestChan = yield actionChannel('REQUEST')
while (true) {
// 2- take from the channel
// const requestChan = yield actionChannel('REQUEST', buffers.sliding(5))
const {payload} = yield take(requestChan)
// 3- Note that we're using a blocking call
yield call(handleRequest, payload)
}
}
function* handleRequest(payload) { ... }
eventChannel
eventChannel用来接收redux store之外的消息
import { take, put, call, cancelled } from 'redux-saga/effects'
import { eventChannel, END } from 'redux-saga'
// creates an event Channel from an interval of seconds
function countdown(seconds) { ... }
export function* saga() {
const chan = yield call(countdown, value)
try {
while (true) {
let seconds = yield take(chan)
console.log(`countdown: ${seconds}`)
}
} finally {
if (yield cancelled()) {
chan.close()
console.log('countdown cancelled')
}
}
}
channel
channel用在多个saga之间通信
import { channel } from 'redux-saga'
import { take, fork, ... } from 'redux-saga/effects'
function* watchRequests() {
// create a channel to queue incoming requests
const chan = yield call(channel)
// create 3 worker 'threads'
for (var i = 0; i < 3; i++) {
yield fork(handleRequest, chan)
}
while (true) {
const {payload} = yield take('REQUEST')
yield put(chan, payload)
}
}
function* handleRequest(chan) {
while (true) {
const payload = yield take(chan)
// process the request
}
}