上一篇文章# redux middleware 的使用与实现原理介绍了
redux middleware
,今天这篇文章将介绍非常流行的一个用于处理Side Effect
的redux middleware
也就是redux-saga
。
在 redux
的 reducer
是一个纯函数,也就是说同样的输入必须产生同样的输出,不能包含任何的 Side Effect
副作用, redux
副作用是指在redux
应用程序中,除了纯粹的状态变更外,对外部环境产生的任何操作或影响。一些常见的副作用包括:
- 异步操作:例如发送网络请求、读取本地存储或访问数据库等。这些操作需要在某个时刻完成,并且可能会导致状态的变化
- 访问浏览器环境:例如修改
URL
路径、访问浏览器的cookies
或localStorage
等。这些操作与redux
的纯函数原则不符,但是在应用中常常需要进行。 - 访问外部资源:例如获取当前时间、获取设备的地理位置等。这些操作也属于副作用,因为它们不是通过纯函数的方式实现的。
- 使用定时器:比如
settimeout
和setinterval
- 生成随机数或者随机id
副作用不能存在于reducer
中,但是项目中又十分常见所以我们可以用一个库来帮我们处理redux
副作用
什么是 redux-saga
redux-saga
是一个用于管理 redux
副作用的库, 它可以使管理副作用更加容易、执行更高效、测试更简单、在发生错误时能更轻易地定位问题。
redux-saga
使用Generator
生成器,让异步流程的代码更易读,也更容易测试。
如何使用
首先肯定要先安装redux-saga
:
npm install redux-saganpm install redux-saganpm install redux-saga
redux-saga
是一个redux
中间件所以要在redux
中使用这个它, 大致代码如下:
import { applyMiddleware } from 'redux';import createSagaMiddleware from 'redux-saga';import saga from './effects'// 通过调用 createSagaMiddlewar 创建 sagaMiddlewareconst sagaMiddleware = createSagaMiddleware();export const store = applyMiddleware(sagaMiddleware)(createStore)(rootReducer);// 调用 sagaMiddleware 的 run 方法传入根 saga, 这个 saga 是一个 generatorsagaMiddleware.run(saga);import { applyMiddleware } from 'redux'; import createSagaMiddleware from 'redux-saga'; import saga from './effects' // 通过调用 createSagaMiddlewar 创建 sagaMiddleware const sagaMiddleware = createSagaMiddleware(); export const store = applyMiddleware(sagaMiddleware)(createStore)(rootReducer); // 调用 sagaMiddleware 的 run 方法传入根 saga, 这个 saga 是一个 generator sagaMiddleware.run(saga);import { applyMiddleware } from 'redux'; import createSagaMiddleware from 'redux-saga'; import saga from './effects' // 通过调用 createSagaMiddlewar 创建 sagaMiddleware const sagaMiddleware = createSagaMiddleware(); export const store = applyMiddleware(sagaMiddleware)(createStore)(rootReducer); // 调用 sagaMiddleware 的 run 方法传入根 saga, 这个 saga 是一个 generator sagaMiddleware.run(saga);
上面的代码通过 redux-saga
默认导出的 createSagaMiddleware
来创建一个 sagaMiddleware
, 接着使用 redux
的 applyMiddleware
方法, 使用这个 sagaMiddleware
。 最后需要做的是 sagaMiddleware.run(saga)
意味着需要执行的一个根 saga
, 接着看下这个根 saga
的内容。
import { take, put } from 'redux-saga/effects';import { SAGA_INCREMENT, SAGA_DECREMENT } from '.';function* sagaIncrementWatcher() {yield take(SAGA_INCREMENT);yield put(increment(2))}function* sagaDecrementWatcher() {yield take(SAGA_DECREMENT)yield put(decrement())}export default function* saga() {yield addSagaAddWatcher();yield addSagaDecrementWatcher();}import { take, put } from 'redux-saga/effects'; import { SAGA_INCREMENT, SAGA_DECREMENT } from '.'; function* sagaIncrementWatcher() { yield take(SAGA_INCREMENT); yield put(increment(2)) } function* sagaDecrementWatcher() { yield take(SAGA_DECREMENT) yield put(decrement()) } export default function* saga() { yield addSagaAddWatcher(); yield addSagaDecrementWatcher(); }import { take, put } from 'redux-saga/effects'; import { SAGA_INCREMENT, SAGA_DECREMENT } from '.'; function* sagaIncrementWatcher() { yield take(SAGA_INCREMENT); yield put(increment(2)) } function* sagaDecrementWatcher() { yield take(SAGA_DECREMENT) yield put(decrement()) } export default function* saga() { yield addSagaAddWatcher(); yield addSagaDecrementWatcher(); }
在这个根 saga
中, fork
了两个生成器函数 sagaIncrementWatcher
和 sagaDecrementWatcher
函数, 这两个生成器函数的内容类似, 都是监听某个 action
动作即 take(actionType)
, 在这个动作执行之后会执行后面的内容触发一个 dispatch
改变 state
, 即 put(increment(2))
注意如果 yield 一个迭代器(在这儿是一个生成器) 那么这个迭代器会阻塞saga的执行, take 也会阻塞 saga 的执行, 所以在上面这个例子中, 没有 SAGA_INCREMENT 这个动作 dispatch 之前 sagaDecrementWatcher 是不会被触发的。
在组件中就可以这么使用:
import { store } from '.'const handleSagaIncrement = () => {store.dispatch({ type: SAGA_INCREMENT })}const handleSagaDecrement = () => {store.dispatch({ type: SAGA_DECREMENT })}import { store } from '.' const handleSagaIncrement = () => { store.dispatch({ type: SAGA_INCREMENT }) } const handleSagaDecrement = () => { store.dispatch({ type: SAGA_DECREMENT }) }import { store } from '.' const handleSagaIncrement = () => { store.dispatch({ type: SAGA_INCREMENT }) } const handleSagaDecrement = () => { store.dispatch({ type: SAGA_DECREMENT }) }
在组件中同样是通过 store.dispatch
一个 action
来触发 saga
中 take
的 actionType
, 从而执行后面的逻辑!
上面代码中的 take
, put
被称为 effect creators
, 这些 effect creators
执行之后实际上就是返回一些简单的 javascript
对象, 我们称这些返回对象为 Effect
, 只不过通过 yield Effect
可以处理不同的逻辑,下面介绍一些常用的 effect creators
take
take
用于指定 saga
等待某一 action
触发, 只有匹配的 action
派发之后才继续执行, 否则将暂停 Generator
函数的执行, 通俗来说就是会阻塞 saga
运行。
take
的参数可以是一个字符串、或者一个函数、或者字符串和函数的数组。传入字符串*
时将会匹配所有action
, 看下面的例子:
function* watchSaga() {// 等待 action { type: 'add' } 被派发, 被派发之后才会继续执行, 否则将暂停执行yield take('add');console.log('add action has been dispatched');}function* watchSaga() { // 等待 action { type: 'add' } 被派发, 被派发之后才会继续执行, 否则将暂停执行 yield take('add'); console.log('add action has been dispatched'); }function* watchSaga() { // 等待 action { type: 'add' } 被派发, 被派发之后才会继续执行, 否则将暂停执行 yield take('add'); console.log('add action has been dispatched'); }
take
也可以接收一个channel
, 这在saga
中处理事件监听非常有用, 比如在saga
中处理websocket
消息的收发等, 详见官方文档Channels
fork(fn, …args)
以非阻塞的方式执行函数 fn
, fn
可以是一个 Generator
函数或者一个返回 Promise
的普通函数, args
即是向 fn
传递的参数
takeEvery
使用方式和 take
差不多, 只不过 takeEvery
会响应多次 action
而 take
只会执行一次, 并且 takeEvery 是非阻塞的
实际上
takeEvery
内部就是通过fork
和不断地调用take
来实现的
put
用于在 saga
中派发动作, 使用方法和 dispatch
一样, 非阻塞
例如派发一个 add
的 action
:
function* foo(){yield put({type: 'add',payload: 3,});}function* foo(){ yield put({ type: 'add', payload: 3, }); }function* foo(){ yield put({ type: 'add', payload: 3, }); }
call(fn, …args)
以 args
作为参数调用 fn
, fn
可以是一个 Generator
函数或者是一个返回Promise
的函数或者其他普通函数。call
会阻塞saga
的执行。
select(selector, …args)
用于通过selector
函数获取state
中的计算值, args
是传给selector
函数的可选参数
all([…effects])
并行地运行多个 Effect
,并等待它们全部完成, 类似于 Promise.all
redux-saga 拥有众多的 effect-creator, 可以通过 redux-saga api 查看。
原理及实现
前置知识
- function* 生成器函数,
function\*
这种声明方式(function关键字后跟一个星号)会定义一个生成器函数 (generator function
),它返回一个Generator
对象。 - Generator 对象 生成器对象是由一个
generator function
返回的,并且它符合可迭代协议和迭代器协议。拥有三个方法:- Generator.prototype.next(value) 返回一个拥有
done
和value
的对象,如果done
为true
表示迭代完毕,反之亦然;value
是在generator
中被yield
的值或者generator
函数的返回值。可以给next
方法传递参数, 这个参数将会称为yield
表达式的值。 - Generator.prototype.return() 返回给定的值并结束生成器。
- Generator.prototype.throw() 向生成器抛出一个错误。
- Generator.prototype.next(value) 返回一个拥有
- co 函数, 自动地迭代一个 Generator 对象。
- 判断一个对象是否是迭代器,拥有
Symbol.iterator
属性的对象就是一个可迭代的对象,Symbol.iterator
通常是一个返回迭代器的函数。
通过 co
函数可以自动的迭代一个生成器函数,一个简单的 co
函数如下:
function co(generator) {const it = generator();function next(iterator) {const { value, done } = iterator.next();if (done) return value;return next(iterator);}return next(it);}function co(generator) { const it = generator(); function next(iterator) { const { value, done } = iterator.next(); if (done) return value; return next(iterator); } return next(it); }function co(generator) { const it = generator(); function next(iterator) { const { value, done } = iterator.next(); if (done) return value; return next(iterator); } return next(it); }
实现
redux-saga
在代码健壮性上做了很多工作比如错误处理, 调度处理等, 在功能上由各种的 effectCreator 来实现。 所以以下代码只是核心原理, 并选择了一些effectCrator
来实现, 并不是源码的一比一还原。
首先 redux-saga
导出一个默认方法 createSagaMiddleware
, 在 createSagaMiddleware
方法中会创建一个中间件 sagaMiddleware
, 这个 sagaMiddleware
中初始化了一个 boundRunSaga
方法, 这个方法是通过给 runSaga
绑定第一个参数 { channel, getState, dispatch }
后返回的一个新方法, 所以在 runSaga
内部能访问这个 { channel, getState, dispatch }
等一些列对象。
其中给 runSaga
绑定的参数对象属性的作用如下:
channel
主要用于发布订阅, 在redux
中一个action
被dispatch
时通知监听该action
的监听方法触发, 在redux
中take effect
会监听action
的dispatch
, 在redux-saga
中自己实现了一套发布订阅, 这里我用比较熟悉的events
包, 它的API
和node
中的events
模块一致。getState
, 这是通过redux
传递进来的参数, 可以获得store
中的state
dispatch
, 同样是通过redux
传递进来的参数, 可以派发动作
import runSaga from './runSaga';import EventEmitter from 'events';function createSagaMiddleware() {// 创建一个 channel 用于在 redux 的某个 action 被派发时通知监听函数const channel = new EventEmitter();let boundRunSaga;function sagaMiddleware(store) {const { getState, dispatch } = store;boundRunSaga = runSaga.bind(null, { channel, getState, dispatch })return function (next) {return function (action) {const result = next(action);channel.emit(action.type, action);return result;}}}sagaMiddleware.run = (saga) => boundRunSaga(saga);return sagaMiddleware;}export default createSagaMiddleware;import runSaga from './runSaga'; import EventEmitter from 'events'; function createSagaMiddleware() { // 创建一个 channel 用于在 redux 的某个 action 被派发时通知监听函数 const channel = new EventEmitter(); let boundRunSaga; function sagaMiddleware(store) { const { getState, dispatch } = store; boundRunSaga = runSaga.bind(null, { channel, getState, dispatch }) return function (next) { return function (action) { const result = next(action); channel.emit(action.type, action); return result; } } } sagaMiddleware.run = (saga) => boundRunSaga(saga); return sagaMiddleware; } export default createSagaMiddleware;import runSaga from './runSaga'; import EventEmitter from 'events'; function createSagaMiddleware() { // 创建一个 channel 用于在 redux 的某个 action 被派发时通知监听函数 const channel = new EventEmitter(); let boundRunSaga; function sagaMiddleware(store) { const { getState, dispatch } = store; boundRunSaga = runSaga.bind(null, { channel, getState, dispatch }) return function (next) { return function (action) { const result = next(action); channel.emit(action.type, action); return result; } } } sagaMiddleware.run = (saga) => boundRunSaga(saga); return sagaMiddleware; } export default createSagaMiddleware;
所以可以看到我们在使用时调用的 sagaMiddleware.run(rootSaga)
其实就是这个 boundRunSaga
, 本质上就是 runSaga
:
import * as effectTypes from './effectTypes';const CANCEL_TASK = 'CANCEL_TASK'; // 用于取消 saga 执行export default function runSaga(env, saga, cb) {// task 最终会被返回, 调用 task.cancel 可以取消 saga 的执行const task = {cancel: () => { next(CANCEL_TASK); },};const { channel, dispatch } = env;const it = typeof saga === 'function' ? saga() : saga;// saga 本质上就是不断地迭代 Generator 使其迭代完成, 在迭代的过程中处理不同的 Effectfunction next(value) {const { value: effect, done } = value === CANCEL_TASK? it.return(value) // 如果派发了 CANCEL_TASK 那么取消执行: it.next(value);if (!done) {if (typeof effect[Symbol.iterator] === 'function') {// 如果迭代的是一个 Generator 那么会调用 runSaga 迭代这个 Generator, 迭代完毕之后才会调用 next// 所以在 saga 中 yield generatorFn() 是阻塞的runSaga(env, effect, next);} else if (effect instanceof Promise) {// 如果迭代的是一个 Promise, 比如 yield fooPromise(), 那么会在这个 promise resolve 之后再继续迭代// 所以 yield fooPromise() 也是阻塞的effect.then(next);} else {// 接下来就是处理 saga 中的各种 effect creators 的返回值switch (effect.type) {case effectTypes.TAKE:// 处理 take, 可以看到使用的是 channel.once 也就是说 take 只会响应一个 action// 并且 take 是阻塞的, 只有在匹配的 action 被派发之后才会继续迭代channel.once(effect.actionType, next);break;case effectTypes.PUT:// 处理 put, 实际上就是调用 dispatchdispatch(effect.action);next(effect.action);break;case effectTypes.FORK:// 处理 fork, 直接调用 runSage 创建一个新的saga, 可以看到 fork 是非阻塞的const forkTask = runSaga(env, effect.saga);// 这里意味着 yield fork(fooSaga) 的表达式的值就是这个 task// 比如 const someTask = yield fork(fooSaga); 此时便可以通过 someTask.cancel() 来取消这个tasknext(forkTask);break;case effectTypes.CALL:// 处理 call, 用 Promise.resolve 来包裹 fn 的执行结果, 如果 fn 返回一个 promise 那么将会将该 promise resolve 的值传递给 Promise.resolvePromise.resolve(effect.fn(...effect.args)).then(next)break;case effectTypes.ALL:// 处理 all, 实现思路和 Promise.all 差不多, 用一个计数器来记录迭代完的 Effect 在全部 Effect 迭代结束之后, 调用 nextconst result = [];let count = 0;effect.iterators.forEach((iterator, index) => {function complete(val) {count += 1;result[index] = val;if (count === effect.iterators.length) {next(result);}}if (typeof iterator[Symbol.iterator] === 'function') {// 如果 Effect 是一个 generator 那么调用 runSagarunSaga(env, iterator, complete);} else {Promise.resolve(iterator.fn(...iterator.args)).then(complete);}});break;case effectTypes.SELECT:// 处理 selectnext(effect.selector(getState(), ...effect.args));break;case effectTypes.CANCEL:effect.task.cancel();next();break;default:break;}}} else {// 这类的 cb 在处理 all 时用到cb && cb(effect);}}next();return task;}import * as effectTypes from './effectTypes'; const CANCEL_TASK = 'CANCEL_TASK'; // 用于取消 saga 执行 export default function runSaga(env, saga, cb) { // task 最终会被返回, 调用 task.cancel 可以取消 saga 的执行 const task = { cancel: () => { next(CANCEL_TASK); }, }; const { channel, dispatch } = env; const it = typeof saga === 'function' ? saga() : saga; // saga 本质上就是不断地迭代 Generator 使其迭代完成, 在迭代的过程中处理不同的 Effect function next(value) { const { value: effect, done } = value === CANCEL_TASK ? it.return(value) // 如果派发了 CANCEL_TASK 那么取消执行 : it.next(value); if (!done) { if (typeof effect[Symbol.iterator] === 'function') { // 如果迭代的是一个 Generator 那么会调用 runSaga 迭代这个 Generator, 迭代完毕之后才会调用 next // 所以在 saga 中 yield generatorFn() 是阻塞的 runSaga(env, effect, next); } else if (effect instanceof Promise) { // 如果迭代的是一个 Promise, 比如 yield fooPromise(), 那么会在这个 promise resolve 之后再继续迭代 // 所以 yield fooPromise() 也是阻塞的 effect.then(next); } else { // 接下来就是处理 saga 中的各种 effect creators 的返回值 switch (effect.type) { case effectTypes.TAKE: // 处理 take, 可以看到使用的是 channel.once 也就是说 take 只会响应一个 action // 并且 take 是阻塞的, 只有在匹配的 action 被派发之后才会继续迭代 channel.once(effect.actionType, next); break; case effectTypes.PUT: // 处理 put, 实际上就是调用 dispatch dispatch(effect.action); next(effect.action); break; case effectTypes.FORK: // 处理 fork, 直接调用 runSage 创建一个新的saga, 可以看到 fork 是非阻塞的 const forkTask = runSaga(env, effect.saga); // 这里意味着 yield fork(fooSaga) 的表达式的值就是这个 task // 比如 const someTask = yield fork(fooSaga); 此时便可以通过 someTask.cancel() 来取消这个task next(forkTask); break; case effectTypes.CALL: // 处理 call, 用 Promise.resolve 来包裹 fn 的执行结果, 如果 fn 返回一个 promise 那么将会将该 promise resolve 的值传递给 Promise.resolve Promise.resolve(effect.fn(...effect.args)) .then(next) break; case effectTypes.ALL: // 处理 all, 实现思路和 Promise.all 差不多, 用一个计数器来记录迭代完的 Effect 在全部 Effect 迭代结束之后, 调用 next const result = []; let count = 0; effect.iterators.forEach((iterator, index) => { function complete(val) { count += 1; result[index] = val; if (count === effect.iterators.length) { next(result); } } if (typeof iterator[Symbol.iterator] === 'function') { // 如果 Effect 是一个 generator 那么调用 runSaga runSaga(env, iterator, complete); } else { Promise.resolve(iterator.fn(...iterator.args)).then(complete); } }); break; case effectTypes.SELECT: // 处理 select next(effect.selector(getState(), ...effect.args)); break; case effectTypes.CANCEL: effect.task.cancel(); next(); break; default: break; } } } else { // 这类的 cb 在处理 all 时用到 cb && cb(effect); } } next(); return task; }import * as effectTypes from './effectTypes'; const CANCEL_TASK = 'CANCEL_TASK'; // 用于取消 saga 执行 export default function runSaga(env, saga, cb) { // task 最终会被返回, 调用 task.cancel 可以取消 saga 的执行 const task = { cancel: () => { next(CANCEL_TASK); }, }; const { channel, dispatch } = env; const it = typeof saga === 'function' ? saga() : saga; // saga 本质上就是不断地迭代 Generator 使其迭代完成, 在迭代的过程中处理不同的 Effect function next(value) { const { value: effect, done } = value === CANCEL_TASK ? it.return(value) // 如果派发了 CANCEL_TASK 那么取消执行 : it.next(value); if (!done) { if (typeof effect[Symbol.iterator] === 'function') { // 如果迭代的是一个 Generator 那么会调用 runSaga 迭代这个 Generator, 迭代完毕之后才会调用 next // 所以在 saga 中 yield generatorFn() 是阻塞的 runSaga(env, effect, next); } else if (effect instanceof Promise) { // 如果迭代的是一个 Promise, 比如 yield fooPromise(), 那么会在这个 promise resolve 之后再继续迭代 // 所以 yield fooPromise() 也是阻塞的 effect.then(next); } else { // 接下来就是处理 saga 中的各种 effect creators 的返回值 switch (effect.type) { case effectTypes.TAKE: // 处理 take, 可以看到使用的是 channel.once 也就是说 take 只会响应一个 action // 并且 take 是阻塞的, 只有在匹配的 action 被派发之后才会继续迭代 channel.once(effect.actionType, next); break; case effectTypes.PUT: // 处理 put, 实际上就是调用 dispatch dispatch(effect.action); next(effect.action); break; case effectTypes.FORK: // 处理 fork, 直接调用 runSage 创建一个新的saga, 可以看到 fork 是非阻塞的 const forkTask = runSaga(env, effect.saga); // 这里意味着 yield fork(fooSaga) 的表达式的值就是这个 task // 比如 const someTask = yield fork(fooSaga); 此时便可以通过 someTask.cancel() 来取消这个task next(forkTask); break; case effectTypes.CALL: // 处理 call, 用 Promise.resolve 来包裹 fn 的执行结果, 如果 fn 返回一个 promise 那么将会将该 promise resolve 的值传递给 Promise.resolve Promise.resolve(effect.fn(...effect.args)) .then(next) break; case effectTypes.ALL: // 处理 all, 实现思路和 Promise.all 差不多, 用一个计数器来记录迭代完的 Effect 在全部 Effect 迭代结束之后, 调用 next const result = []; let count = 0; effect.iterators.forEach((iterator, index) => { function complete(val) { count += 1; result[index] = val; if (count === effect.iterators.length) { next(result); } } if (typeof iterator[Symbol.iterator] === 'function') { // 如果 Effect 是一个 generator 那么调用 runSaga runSaga(env, iterator, complete); } else { Promise.resolve(iterator.fn(...iterator.args)).then(complete); } }); break; case effectTypes.SELECT: // 处理 select next(effect.selector(getState(), ...effect.args)); break; case effectTypes.CANCEL: effect.task.cancel(); next(); break; default: break; } } } else { // 这类的 cb 在处理 all 时用到 cb && cb(effect); } } next(); return task; }
分析以上代码 runSaga
方法其实就是一个自动迭代生成器的这么一个方法, 它接受三个参数一个是 env
, 也就是在 createSagaMiddleware
中 bind
的那个参数{ channel, getState, dispatch }
。第二个参数是一个 saga
, 可以是一个生成器函数也可以是一个迭代器。 第三个参数是一个 callback
, 用于在迭代完成之后的回调。其中在每次迭代时 effect
就是这个迭代器执行 it.next()
返回的 value
值, effect 的类型可能是这些:
effect
可能是一个迭代器, 比如generate
函数的执行结果, 如果是一个迭代器的话那么我们会将这个这个迭代器再使用runSaga
进行迭代, 并且将next
方法传入, 在effect
迭代完成之后会执行next
方法, 那么此时就能继续迭代saga
了effect
可能是一个Promise
, 那么在Promise.then
方法中传入next
, 使得在Promise resolve
之后能继续迭代effect
可能是通过内部方法创建的对象, 那么就要根据不同的effect
对象处理不同的逻辑
针对不同的 effect
进行了相应的处理, effect
就是我们使用的从 redux-saga/effects
中导入的函数, 比如 take, put, fork, call
等。在内部这些方法就是返回一些对象, 最终给到 runSaga
函数来处理。
其中 effect
对象的 type
有这么一些值:
export const TAKE = 'TAKE'; // 对应 take 方法export const PUT = 'PUT'; // 对应 put 方法export const FORK = 'FORK'; // 对应 fork 方法export const CALL = 'CALL'; // 对应 call 方法export const ALL = 'ALL'; // 对应 all 方法export const SELECT = 'SELECT'; // 对应 select 方法export const CANCEL = 'CANCEL'; // 对应 cancel 方法export const TAKE = 'TAKE'; // 对应 take 方法 export const PUT = 'PUT'; // 对应 put 方法 export const FORK = 'FORK'; // 对应 fork 方法 export const CALL = 'CALL'; // 对应 call 方法 export const ALL = 'ALL'; // 对应 all 方法 export const SELECT = 'SELECT'; // 对应 select 方法 export const CANCEL = 'CANCEL'; // 对应 cancel 方法export const TAKE = 'TAKE'; // 对应 take 方法 export const PUT = 'PUT'; // 对应 put 方法 export const FORK = 'FORK'; // 对应 fork 方法 export const CALL = 'CALL'; // 对应 call 方法 export const ALL = 'ALL'; // 对应 all 方法 export const SELECT = 'SELECT'; // 对应 select 方法 export const CANCEL = 'CANCEL'; // 对应 cancel 方法
各种 effect 方法:
import * as effectTypes from './effectTypes';export function take(actionType) {return { type: effectTypes.TAKE, actionType }}export function put(action) {return { type: effectTypes.PUT, action }}export function fork(saga, ...args) {return { type: effectTypes.FORK, saga: saga.bind(null, ...args) }}export function takeEvery(actionType, saga) {function* takeEveryHelper() {while (true) {const action = yield take(actionType);yield fork(saga, action); // saga 是一个生成器函数, 所以使用 fork 来 run 这个生成器函数}}return fork(takeEveryHelper);}export function call(fn, ...args) {return {type: effectTypes.CALL, fn, args}}export function all(iterators) {return {type: effectTypes.ALL, iterators}}export function cancel(task) {return {type: effectTypes.CANCEL, task}}export function delay(ms) {return new Promise(r => setTimeout(() => {r()}, ms))}import * as effectTypes from './effectTypes'; export function take(actionType) { return { type: effectTypes.TAKE, actionType } } export function put(action) { return { type: effectTypes.PUT, action } } export function fork(saga, ...args) { return { type: effectTypes.FORK, saga: saga.bind(null, ...args) } } export function takeEvery(actionType, saga) { function* takeEveryHelper() { while (true) { const action = yield take(actionType); yield fork(saga, action); // saga 是一个生成器函数, 所以使用 fork 来 run 这个生成器函数 } } return fork(takeEveryHelper); } export function call(fn, ...args) { return { type: effectTypes.CALL, fn, args } } export function all(iterators) { return { type: effectTypes.ALL, iterators } } export function cancel(task) { return { type: effectTypes.CANCEL, task } } export function delay(ms) { return new Promise(r => setTimeout(() => { r() }, ms)) }import * as effectTypes from './effectTypes'; export function take(actionType) { return { type: effectTypes.TAKE, actionType } } export function put(action) { return { type: effectTypes.PUT, action } } export function fork(saga, ...args) { return { type: effectTypes.FORK, saga: saga.bind(null, ...args) } } export function takeEvery(actionType, saga) { function* takeEveryHelper() { while (true) { const action = yield take(actionType); yield fork(saga, action); // saga 是一个生成器函数, 所以使用 fork 来 run 这个生成器函数 } } return fork(takeEveryHelper); } export function call(fn, ...args) { return { type: effectTypes.CALL, fn, args } } export function all(iterators) { return { type: effectTypes.ALL, iterators } } export function cancel(task) { return { type: effectTypes.CANCEL, task } } export function delay(ms) { return new Promise(r => setTimeout(() => { r() }, ms)) }
结合以上代码我们具体分析一下各个 effect creator
的实现
take effect creator 实现
当我们调用 take
函数时, 会传入 actionType
, 也就是 dispatch(action)
中的 action.type
, 在 runSaga
中会调用 channel
的 once
函数来监听对应的 dispatch(action)
动作, 在 sagaMiddleware
中间件中会通过 channel.emit(action.type, action)
来触发这个监听函数, 在监听函数中会调用 next
函数从而继续迭代 saga
。
put effect creator 实现
在调用 put
函数时, 会传入对应的 action
, 在 runSaga
中可以通过 env
对象拿到 dispatch
函数从而去改变 store
中的 state
fork effect creator 实现
fork
接收一个生成器函数 fn
作为第一个参数, 后面的可以传递任意的参数, 将会作为 fn
的参数进行调用。在 runSaga
中迭代到 fork
时会将这个 fn
作为参数传递给 runSaga
, 并且迭代这个 fn
生成器函数并不会阻塞当前的 saga
执行。当前的 saga
继续迭代。
call effect creator 实现
call
接收函数 fn
作为第一个参数, 该函数可以是一个返回 promise
的函数也可以是一个普通函数, 后面的可以传递任意的参数, 将会作为 fn
的参数进行调用。 在 runSaga
中如果迭代到 call
时, 会调用 fn
并传入对应的参数, 并且用 Promise.resolve
包裹 fn
的返回结果, 这样无论 fn
的返回结果是不是一个 Promise
都会包装成一个 Promise
, 并且在该 Promise
的 then
方法中调用 next
继续迭代 saga
。
takeEvery effect creator 实现
takeEvery
接收两个参数, 第一个参数是需要监听的 action.type
, 第二个参数是一个生成器函数 workerSaga
用于处理监听到的 action.type
。takeEvery
内部使用 fork
和 take
来实现, 首先返回一个 fork
以非阻塞的方式迭代 takeEveryHelper
, takeEveryHelper
通过 take
来监控 actiontType
, 并再次通过 fork
来迭代 workerSaga
。
function takeEvery(actionType, saga) {function* takeEveryHelper() {while (true) {const action = yield take(actionType);yield fork(saga, action); // saga 是一个生成器函数, 所以使用 fork 来 run 这个生成器函数}}return fork(takeEveryHelper);}function takeEvery(actionType, saga) { function* takeEveryHelper() { while (true) { const action = yield take(actionType); yield fork(saga, action); // saga 是一个生成器函数, 所以使用 fork 来 run 这个生成器函数 } } return fork(takeEveryHelper); }function takeEvery(actionType, saga) { function* takeEveryHelper() { while (true) { const action = yield take(actionType); yield fork(saga, action); // saga 是一个生成器函数, 所以使用 fork 来 run 这个生成器函数 } } return fork(takeEveryHelper); }
all effect creator 实现
all
接收一个数组, 该数组的成员可以是迭代器, 也可以是通过 call
函数返回的对象, 类似于 Promise.all
, all
函数会在所有的成员并发地进行迭代, 并且在每个成员迭代完之后将迭代的结果返回。在 runSaga
中会遍历数组中的成员, 并使用一个指针 count
来记录当前迭代完成的数量, 在所有的成员都迭代完成之后会调用 next
, 继续迭代 saga
。并且在迭代成员时做了一个判断, 如果是迭代器那么用 runSaga
方法来进行迭代, 并且传入 complete
函数作为 callback
; 如果是一个 Promise
那么在 Promise.then
方法中调用 next
select effectCreator 实现
select
接收一个 selector
作为第一个参数,后面的可以传递任意的参数,将作为 selector
函数的第二个参数及第二个参数后面的参数。在 runSaga
中如果迭代到了 select
则会将 env
中 getState()
执行结果及 select
传入的参数作为 selector
的参数执行,最后调用 next
方法传入得到的结果,这样就能在 yield
左侧拿到查询结果了
cancel effectCreator 实现
在 fork
或者 takeEvery
调用之后会返回一个 task
对象,调用 cancel(task)
函数可以取消执行该任务。所以 runSaga
函数需要返回一个 task
对象,该对象拥有一个 cancel
方法,这个 cancel
方法执行后会调用 next
函数,并传入一个 CANCEL_TASK
常量,在 next
函数中判断如果传入的 value
是 CANCEL_TASK
那么就调用 it.return()
将这个迭代器迭代完毕,之后就不再迭代了。并且在 fork
这个 effect
中将 runSaga
的返回对象 task
作为参数传递给 next
方法,最终 yield fork()
的值就是这个 task
了。而在 runSaga
中如果 effect
是 CANCEL
的话直接调用 task.cancel()
总结
以上就是 redux-saga
的使用与实现原理的全部内容, 篇幅有限文中还有很多 redux-saga
的 API
没有讲到, 纸上得来终觉浅,绝知此事要躬行, 还需要大家自己在项目中学习和总结。