Redux-saga是redux应用的又一个副作用模型。可以用来替换redux-thunk中间件。 redux-saga 抽象出 Effect (影响, 例如等待action、发出action、fetch数据等等),便于组合与测试。

我想在分析redux-saga之前,先来看看redux-thunk是怎么一回事 redux 在我之前一篇文章中讲过了链接 那我们就先用 redux-thunk 来写一个 asyncTodo 的demo

redux-thunk 分析

import { createStore, applyMiddleware } from 'redux';
const thunk = ({ dispatch, getState }) => next => action => {if (typeof action === 'function') {return action(dispatch, getState);}return next(action);
}const logger = ({ getState }) => next => action => {console.log('will dispatch', getState());next(action)console.log('state after dispatch', getState());
}const todos = (state = [], action) => {switch (action.type) {case 'ADD_TODO':return [...state,action.text];default:return state}
}const store = createStore(todos,['Use Redux'],applyMiddleware(logger, thunk),
);store.dispatch(dispatch => {setTimeout(() => {dispatch({ type: 'ADD_TODO', text: 'Read the docs' });}, 1000);
});
复制代码

原本redux中action只能是 plain object ,redux-thunk使action可以为function。当我们想抛出一个异步的action时,其实我们是把异步的处理放在了actionCreator中。 这样就会导致action形式不统一,并且对于异步的处理将会分散到各个action中,不利于维护。 接下来看看redux-saga是如何实现的

redux-saga

import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import { put, take, fork, delay } from './redux-saga/effects'
import { delay as delayUtil } from 'redux-saga/utils';// 获取redux中间件
const sagaMiddleware = createSagaMiddleware({sagaMonitor: {// 打印 effect 便于分析redux-saga行为effectTriggered(options) {console.log(options);}}
})function* rootSaga() {const action = yield take('ADD_TODO_SAGA');// delay(): { type: 'call', payload: { args: [1000], fn }}yield delay(1000); // or yield call(delayUtil, 1000)// put(): { type: 'PUT', payload: { action: {}, channel: null }}yield put({ type: 'ADD_TODO', text: action.text  });
}const store = createStore(todos,['Use Redux'],applyMiddleware(logger, sagaMiddleware),
);// 启动saga
sagaMiddleware.run(rootSaga);store.dispatch({ type: 'ADD_TODO_SAGA', text: 'Use Redux-saga' });
复制代码

可以看到这里抛出的就是一个纯action, saga在启动之后监听 ADD_TODO_SAGA 事件,若事件发生执行后续代码。

源码

stdChannel

在开始createSagaMiddleware之前,先来了解一下 channel redux-saga 通过 channel 接收与发出action与外部进行数据交换 在redux-saga中有三种 channel,分别是channel、eventChannel、multicastChannel; 在此我们仅仅分析一下用的最多的 multicastChannel

export function multicastChannel() {let closed = false// 这里taker分为的currentTakers、 nextTakers的原因和redux subscribe类似,防止在遍历taker时,taker发生变化。let currentTakers = []let nextTakers = currentTakersconst ensureCanMutateNextTakers = () => {if (nextTakers !== currentTakers) {return}nextTakers = currentTakers.slice()}const close = () => {closed = trueconst takers = (currentTakers = nextTakers)for (let i = 0; i < takers.length; i++) {const taker = takers[i]taker(END)}nextTakers = []}return {[MULTICAST]: true,put(input) {if (closed) {return}if (isEnd(input)) {close()return}const takers = (currentTakers = nextTakers)// 遍历takers,找到与input匹配的taker并执行它。for (let i = 0; i < takers.length; i++) {const taker = takers[i]if (taker[MATCH](input)) {taker.cancel()taker(input)}}},// 存下callback,与配置函数take(cb, matcher = matchers.wildcard) {if (closed) {cb(END)return}cb[MATCH] = matcherensureCanMutateNextTakers()nextTakers.push(cb)cb.cancel = once(() => {ensureCanMutateNextTakers()remove(nextTakers, cb)})},close,}
}export function stdChannel() {const chan = multicastChannel()const { put } = chanchan.put = input => {if (input[SAGA_ACTION]) {put(input)return}// 暂时不用管asap(() => put(input))}return chan
}
复制代码

createSagaMiddleware

获取redux-middleware, 同时初始化runsaga函数,为后面启动saga bind 所需的参数

export default function sagaMiddlewareFactory({ context = {}, ...options } = {}) {const { sagaMonitor, logger, onError, effectMiddlewares } = optionslet boundRunSaga// redux middlewarefunction sagaMiddleware({ getState, dispatch }) {// 新建一个channelconst channel = stdChannel()channel.put = (options.emitter || identity)(channel.put)boundRunSaga = runSaga.bind(null, {context,channel,dispatch,getState,sagaMonitor,logger,onError,effectMiddlewares,})return next => action => {if (sagaMonitor && sagaMonitor.actionDispatched) {sagaMonitor.actionDispatched(action)}const result = next(action) // hit reducers// 将事件传递给sagachannel.put(action)return result}}// 启动sagasagaMiddleware.run = (...args) => {// ...return boundRunSaga(...args)}//...return sagaMiddleware
}
复制代码

runsaga

export function runSaga(options, saga, ...args) {// generate iteratorconst iterator = saga(...args)const {channel = stdChannel(),dispatch,getState,context = {},sagaMonitor,logger,effectMiddlewares,onError,} = optionsconst effectId = nextSagaId()// 一些错误检查// ...const log = logger || _logconst logError = err => {log('error', err)if (err && err.sagaStack) {log('error', err.sagaStack)}}const middleware = effectMiddlewares && compose(...effectMiddlewares)// 可以先理解为 finalizeRunEffect = runEffect => runEffectconst finalizeRunEffect = runEffect => {if (is.func(middleware)) {return function finalRunEffect(effect, effectId, currCb) {const plainRunEffect = eff => runEffect(eff, effectId, currCb)return middleware(plainRunEffect)(effect)}} else {return runEffect}}const env = {stdChannel: channel,dispatch: wrapSagaDispatch(dispatch),getState,sagaMonitor,logError,onError,finalizeRunEffect,}// 新建task,作用是控制 Generator 流程,类似与自动流程管理,这个后面会讲到const task = proc(env, iterator, context, effectId, getMetaInfo(saga), null)return task
}
复制代码

redux-saga的核心就是task, 控制generator函数saga执行流程。是一个复杂的自动流程管理,我们先看一个简单的自动流程管理

// 一个返回promise的delay函数
const delay = (ms) => {return new Promise((res) => {setTimeout(res, ms);});
}function *main() {yield delay(1000);console.log('1s later');yield delay(2000);console.log('done');
}// 为了达到想要的执行结果,我们必须在promise resolved之后再执行next statement,比如这样
const gen = main();
const r1 = gen.next();
r1.value.then(() => {const r2 = gen.next();r2.value.then(() => {gen.next();})
})
复制代码

使用递归实现,自动流程控制

function autoRun(gfunc) {const gen = gfunc();function next() {const res = gen.next();if (res.done) return;res.value.then(next);}next();
}
autoRun(main);
复制代码

上面的自动流程控制函数仅仅支持 promise。

proc

export default function proc(env, iterator, parentContext, parentEffectId, meta, cont) {// ...const task = newTask(parentEffectId, meta, cont)const mainTask = { meta, cancel: cancelMain, _isRunning: true, _isCancelled: false }// 构建 task treeconst taskQueue = forkQueue(mainTask,function onAbort() {cancelledDueToErrorTasks.push(...taskQueue.getTaskNames())},end,)next()// then return the task descriptor to the callerreturn taskfunction next(arg, isErr) {let resultif (isErr) {result = iterator.throw(arg)} else if (shouldCancel(arg)) {// ...} else if (shouldTerminate(arg)) {// ...} else {result = iterator.next(arg)}if (!result.done) {// 如果没结束, 执行相应 effectdigestEffect(result.value, parentEffectId, '', next)} else {/**This Generator has ended, terminate the main task and notify the fork queue**/mainTask._isRunning = falsemainTask.cont(result.value)}}function digestEffect(effect, parentEffectId, label = '', cb) {// 封装了cb函数 增加了事件钩子function currCb(res, isErr) {if (effectSettled) {return}effectSettled = truecb.cancel = noop // defensive measureif (env.sagaMonitor) {if (isErr) {env.sagaMonitor.effectRejected(effectId, res)} else {env.sagaMonitor.effectResolved(effectId, res)}}if (isErr) {crashedEffect = effect}cb(res, isErr)}runEffect(effect, effectId, currCb)}// 每个 effect 的执行函数 这里先看一下常用的几个effectfunction runEffect(effect, effectId, currCb) {if (is.promise(effect)) {resolvePromise(effect, currCb)} else if (is.iterator(effect)) {resolveIterator(effect, effectId, meta, currCb)} else if (effect && effect[IO]) {const { type, payload } = effectif (type === effectTypes.TAKE) runTakeEffect(payload, currCb)else if (type === effectTypes.PUT) runPutEffect(payload, currCb)else if (type === effectTypes.CALL) runCallEffect(payload, effectId, currCb)// 其他所有的effect ...else currCb(effect)} else {// anything else returned as iscurrCb(effect)}}// 当返回值是 promise 时,就和之前实现的自动进程控制函数一样嘛function resolvePromise(promise, cb) {// ...promise.then(cb, error => cb(error, true))}// 当是generator函数时function resolveIterator(iterator, effectId, meta, cb) {proc(env, iterator, taskContext, effectId, meta, cb)}// 当是 take 就把callback放在channel里,如果有匹配事件发生,触发 callbackfunction runTakeEffect({ channel = env.stdChannel, pattern, maybe }, cb) {const takeCb = input => {if (input instanceof Error) {cb(input, true)return}if (isEnd(input) && !maybe) {cb(TERMINATE)return}cb(input)}try {channel.take(takeCb, is.notUndef(pattern) ? matcher(pattern) : null)} catch (err) {cb(err, true)return}cb.cancel = takeCb.cancel}function runPutEffect({ channel, action, resolve }, cb) {asap(() => {let resulttry {// 发送 actionresult = (channel ? channel.put : env.dispatch)(action)} catch (error) {cb(error, true)return}if (resolve && is.promise(result)) {resolvePromise(result, cb)} else {cb(result)}})// put 是不能取消的}function runCallEffect({ context, fn, args }, effectId, cb) {let resulttry {result = fn.apply(context, args)} catch (error) {cb(error, true)return}return is.promise(result)? resolvePromise(result, cb): is.iterator(result)? resolveIterator(result, effectId, getMetaInfo(fn), cb): cb(result)}
}
复制代码

总结

redux-saga 将异步操作抽象为 effect,利用 generator 函数,控制saga流程。 到目前为止,只是涉及了一些基本流程,下一篇会对本篇进行补充。

转载于:https://juejin.im/post/5bb62b2f6fb9a05ce469dcfd

redux-saga源码解析相关推荐

  1. redux provider源码解析

    2019独角兽企业重金招聘Python工程师标准>>> provider 源码 import { Component, Children } from 'react' import ...

  2. Redux 源码解析系列(一) -- Redux的实现思想

    文章来源: IMweb前端社区 黄qiong(imweb.io) IMweb团队正在招聘啦,简历发至jayccchen@tencent.com Redux 其实是用来帮我们管理状态的一个框架,它暴露给 ...

  3. Redux异步解决方案之Redux-Thunk原理及源码解析

    前段时间,我们写了一篇Redux源码分析的文章,也分析了跟React连接的库React-Redux的源码实现.但是在Redux的生态中还有一个很重要的部分没有涉及到,那就是Redux的异步解决方案.本 ...

  4. redux 思考以及源码解析

    1. 基本概念 redux有以下几个基本概念: 1.1. action action: 是一个对象,对一个行为的基本描述 {type:'add',todo } 1.2 action creator 一 ...

  5. react redux 简化_Redux 源码解析

    前言 近日,出于对 Redux 的好奇和想对自己用过的东西知根知底之目的,做了一个 Redux 的自我检测,以便彻底了解其精髓之处.下面我会对使用 Redux 之后产生的疑问做一个清单,用问题导向往下 ...

  6. Redux 异步数据流-- thunk中间件源码解析

    Thunk 引入背景 这是一个关于Redux异步数据流的故事.引入thunk中间件的完整故事在Redux官方中文文档异步数据流.一句话总结就是:原生Redux只支持同步数据流,所以需要引入中间件(mi ...

  7. usestate中的回调函数_React Hooks 源码解析(3):useState

    React 源码版本: v16.11.0 源码注释笔记: airingursb/react​github.com 在写本文之前,事先阅读了网上了一些文章,关于 Hooks 的源码解析要么过于浅显.要么 ...

  8. webpack那些事:浅入深出-源码解析构建优化

    基础知识回顾 入口(entry) module.exports = {entry: './path/to/my/entry/file.js' }; //或者 module.exports = {ent ...

  9. Flutter 路由源码解析

    前言 这一次,我尝试以不贴一行源代码的方式向你介绍 Flutter 路由的实现原理,同时为了提高你阅读源码的积极性,除了原理介绍以外,又补充了两个新的模块:从源码中学习到的编程技巧,以及 阅读源码之后 ...

  10. 【阅读笔记】Taro转小程序编译源码解析

    前言 这篇文章的主要是对taro/taro-tarnsformer-wx进行源码解析,对于想要了解Taro或者了解babel的人希望看了能得到一定的启发. 由于我文笔实在太烂,所以整篇文章都是以阅读笔 ...

最新文章

  1. 安装hadoop下的sqoop1.99.3及配置问题全解决
  2. AutoML前沿技术与实践经验分享 | 免费公开课
  3. IPVS-DR+keepalived实现高可用负载均衡集群
  4. 浅析 Mybatis 与 Hibernate 的区别与用途
  5. bzoj2007: [Noi2010]海拔
  6. nginx一 之负载均衡介绍
  7. 计算机与技术卓越信息技术,2018版计算机科学与技术专业(卓越工程师)培养目标与毕业要求...
  8. linux安装虚拟环境virtualenv
  9. oracle11g 导出表报EXP-00011:table不存在。
  10. 一、JSP的基本原理
  11. python文本数据处理_python 数据处理 对txt文件进行数据处理
  12. 数据提交到服务器后,在中间层所做的修改,能及时反应到客户端吗?
  13. 网络蜘蛛采用三种策略来决定抓取网页的先后顺序
  14. 非线性控制2.0——鲁棒控制之H无穷控制器设计
  15. matlab解全微分方程,通过MATLAB求二阶全微分方程解析解
  16. Linux nanomsg tcp sub pub
  17. wps-doc文件输出为pdf文件时目录报错“错误!未定义书签”解决方法
  18. Linux添加用户及用户权限管理
  19. 磁盘显示数据错误循环冗余检查资料怎么寻回
  20. vue在线预览excel

热门文章

  1. Atitit  信息管理 艾提拉著 CAPT2 数据存储与分类 聚集.docx 目录 1. 按照存储位置 1 1.1. 网盘 1 1.2. 存储在eml imap中 方便检索 1 1.3. 分散与
  2. atitit.跨平台gui 概览
  3. Atitit. 软件设计 模式 变量 方法 命名最佳实践 vp820 attilax总结命名表大全
  4. Atitit.软件开发概念(11)--网络子系统--url编码 空格问题URLEncoder java js php
  5. paip.提升性能---并行多核编程哈的数据结构list,set,map
  6. paip.提升用户体验---业务SQL注入漏洞的分析与解决
  7. 监管科技崛起:从FinTech到RegTech
  8. Rust : 独一无二的Some
  9. ZStack GPU解决方案
  10. 【回归预测】基于matlab离群鲁棒极限学习机(ORELM)求解回归预测问题【含Matlab源码 1441期】