We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
看了下 redux-saga 源码,整理一下个人理解。
源码版本 1.1.3 。
redux-saga 是一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的库,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更方便。redux-saga 使用了 ES6 的 Generator 功能,让异步的流程更易于读取,写入和测试。目前中文文档跟英文并不是完全同步,但可以对照当做参考。中文文档见这里,英文文档见这里。
Generator
redux-saga 是 redux 的一个插件,先理解 redux 的基本原理,有助于理解 redux-saga 的部分逻辑。关于 redux 的解读可以参考之前的这篇文章。
下面结合官方文档中的计数器例子,对 redux-saga 的一个运作方式做一个概述。需要注意的是,这个例子只是一个半成品,需要按照文档中的引导说明,添加后续的逻辑。这个是个人尝试的库。
index.js
import React from 'react'; import ReactDOM from 'react-dom'; import { createStore, applyMiddleware } from 'redux'; import createSagaMiddleware from 'redux-saga'; import reducer from './reducers'; import {watchIncrementAsync} from './sagas'; import App from './App'; const sagaMiddleware = createSagaMiddleware(); const store = createStore(reducer,applyMiddleware(sagaMiddleware)); sagaMiddleware.run(watchIncrementAsync); function render() { ReactDOM.render( <App {...store} />, document.getElementById('root') ); } render(); store.subscribe(render);
reducers.js
export default function counter(state = 0, action) { switch (action.type) { case 'INCREMENT': return state + 1 case 'INCREMENT_IF_ODD': return (state % 2 !== 0) ? state + 1 : state case 'DECREMENT': return state - 1 default: return state } }
sagas.js
import { put, takeEvery } from 'redux-saga/effects' const delay = (ms) => new Promise(res => setTimeout(res, ms)) export function* incrementAsync() { yield delay(3000) yield put({ type: 'INCREMENT' }) } export function* watchIncrementAsync() { yield takeEvery('INCREMENT_ASYNC', incrementAsync) }
App.js
import React from 'react'; import Counter from './component/counter' function App(props) { const {getState,dispatch} = props; return ( <div> <Counter value={getState()} onIncrement={() => dispatch({type:'INCREMENT'})} onDecrement={() => dispatch({type:'DECREMENT'})} onIncrementAsync={() => dispatch({type:'INCREMENT_ASYNC'})} /> </div> ); } export default App;
Counter
import React from 'react' const Counter = ({ value, onIncrement, onDecrement,onIncrementAsync }) => <div> <button onClick={onIncrementAsync}> Increment after 3 second </button> {' '} <button onClick={onIncrement}> Increment </button> {' '} <button onClick={onDecrement}> Decrement </button> <hr /> <div> Clicked: {value} times </div> </div> export default Counter
在 redux 初始化前,使用 createSagaMiddleware 方法创建了中间件,所在源文件为 middleware.js 。主要逻辑代码如下:
createSagaMiddleware
import { assignWithSymbols } from './utils' import { stdChannel } from './channel' import { runSaga } from './runSaga' export default function sagaMiddlewareFactory({ context = {}, channel = stdChannel(), sagaMonitor, ...options } = {}) { let boundRunSaga function sagaMiddleware({ getState, dispatch }) { // 主要运行方法 boundRunSaga = runSaga.bind(null, { ...options, context, channel, dispatch, getState, sagaMonitor, }) return next => action => { // 监听事件相关 if (sagaMonitor && sagaMonitor.actionDispatched) { sagaMonitor.actionDispatched(action) } const result = next(action) // hit reducers channel.put(action) return result } } // 处理副作用函数 sagaMiddleware.run = (...args) => { return boundRunSaga(...args) } // 扩张上下文 sagaMiddleware.setContext = props => { assignWithSymbols(context, props) } return sagaMiddleware }
可以看到返回了符合 redux 中间件格式的函数,在 redux 初始化执行的函数是这样的:
applyMiddleware(sagaMiddleware)(createStore)(reducer, preloadedState) // 将其展开得到 redux 的 dispatch 值 dispatch = action => { if (sagaMonitor && sagaMonitor.actionDispatched) { sagaMonitor.actionDispatched(action) } const result = next(action) // hit reducers channel.put(action) return result }
这样就跟 redux 原有的对应关系保持了一致。
接着在 redux 初始化之后,执行了中间件自带的 run 方法,且传入了专门用来处理副作用的方法。实际执行方法所在源文件为 runSaga.js 。主要逻辑代码如下:
run
import { compose } from 'redux' import proc from './proc' import { stdChannel } from './channel' import { immediately } from './scheduler' import nextSagaId from './uid' import {, logError, noop, wrapSagaDispatch, identity, getMetaInfo } from './utils' export function runSaga( { channel = stdChannel(), dispatch, getState, context = {}, sagaMonitor, effectMiddlewares, onError = logError }, saga, ...args ) { // 传入的副作用处理函数 const iterator = saga(...args) // 生成唯一的标识 const effectId = nextSagaId() // 监听事件相关 if (sagaMonitor) { // monitors are expected to have a certain interface, let's fill-in any missing ones sagaMonitor.rootSagaStarted = sagaMonitor.rootSagaStarted || noop sagaMonitor.effectTriggered = sagaMonitor.effectTriggered || noop sagaMonitor.effectResolved = sagaMonitor.effectResolved || noop sagaMonitor.effectRejected = sagaMonitor.effectRejected || noop sagaMonitor.effectCancelled = sagaMonitor.effectCancelled || noop sagaMonitor.actionDispatched = sagaMonitor.actionDispatched || noop sagaMonitor.rootSagaStarted({ effectId, saga, args }) } let finalizeRunEffect if (effectMiddlewares) { const middleware = compose(...effectMiddlewares) finalizeRunEffect = runEffect => { return (effect, effectId, currCb) => { const plainRunEffect = eff => runEffect(eff, effectId, currCb) return middleware(plainRunEffect)(effect) } } } else { finalizeRunEffect = identity } const env = { channel, dispatch: wrapSagaDispatch(dispatch), getState, sagaMonitor, onError, finalizeRunEffect, } // 记录状态并立即执行 return immediately(() => { const task = proc(env, iterator, context, effectId, getMetaInfo(saga), /* isRoot */ true, undefined) if (sagaMonitor) { sagaMonitor.effectResolved(effectId, task) } return task }) }
该方法处理的逻辑有:
dispatch
SAGA_ACTION
task
在计数器示例中,可以看到无论是不是触发副作用,都统一的使用了 redux 初始化后的 dispatch 的方法。这个也是前面 redux-saga 执行 run 方法的结果。在初始化代码中断点,发现主要的逻辑在下面两句:
const result = next(action) // hit reducers channel.put(action)
触发的时候,首先在 redux 的 redusers 里面匹配是否有对应的 action ,有的话就执行,按照 redux 的逻辑改变 state ;如果没有,则 state 值不变 ,就会到 redux-saga 中进行处理。 channel 对象初始化方法所在源文件为 channel.js 。主要逻辑代码如下:
redusers
action
state
channel
export function stdChannel() { // 初始化对象和方法 const chan = multicastChannel() const { put } = chan chan.put = input => { // 匹配是否在声明的 sagas 文件内 if (input[SAGA_ACTION]) { put(input) return } /* * 放入一个队列中,根据状态决定是否立即执行, * 计数器示例加减时虽然也执行了,但在 put 方法中实际上没有匹配到对应的方法,相当于没有执行 */ asap(() => { put(input) }) } return chan }
通过计数器的例子,可以发现:
take
put
最近看了《反叛的鲁路修》,这部作品在很早之前就听说过,但曾经看过一次之后,发现机甲打斗蛮多,就没什么兴致看下去。
这么多年后,这次忍着看了下去,发现剧情还是蛮好的。动作摆起来感觉有点 JOJO 的风味。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
目录
引子
看了下 redux-saga 源码,整理一下个人理解。
源码版本 1.1.3 。
简介
redux-saga 是一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的库,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更方便。redux-saga 使用了 ES6 的
Generator
功能,让异步的流程更易于读取,写入和测试。目前中文文档跟英文并不是完全同步,但可以对照当做参考。中文文档见这里,英文文档见这里。redux-saga 是 redux 的一个插件,先理解 redux 的基本原理,有助于理解 redux-saga 的部分逻辑。关于 redux 的解读可以参考之前的这篇文章。
下面结合官方文档中的计数器例子,对 redux-saga 的一个运作方式做一个概述。需要注意的是,这个例子只是一个半成品,需要按照文档中的引导说明,添加后续的逻辑。这个是个人尝试的库。
源码
计数器示例
index.js
reducers.js
sagas.js
App.js
Counter
初始化
在 redux 初始化前,使用
createSagaMiddleware
方法创建了中间件,所在源文件为 middleware.js 。主要逻辑代码如下:可以看到返回了符合 redux 中间件格式的函数,在 redux 初始化执行的函数是这样的:
这样就跟 redux 原有的对应关系保持了一致。
接着在 redux 初始化之后,执行了中间件自带的
run
方法,且传入了专门用来处理副作用的方法。实际执行方法所在源文件为 runSaga.js 。主要逻辑代码如下:该方法处理的逻辑有:
dispatch
合并,并赋给了一个全局变量SAGA_ACTION
。在计数器示例中,如果不执行这步,将会无法触发对应逻辑。task
对象,里面包含了任务的相关信息和方法。执行时
在计数器示例中,可以看到无论是不是触发副作用,都统一的使用了 redux 初始化后的
dispatch
的方法。这个也是前面 redux-saga 执行run
方法的结果。在初始化代码中断点,发现主要的逻辑在下面两句:触发的时候,首先在 redux 的
redusers
里面匹配是否有对应的action
,有的话就执行,按照 redux 的逻辑改变state
;如果没有,则state
值不变 ,就会到 redux-saga 中进行处理。channel
对象初始化方法所在源文件为 channel.js 。主要逻辑代码如下:小结
通过计数器的例子,可以发现:
run
方法让针对 saga 进行分类,并关联 redux 的dispatch
方法。take
方法进行注册,然后在put
中循环匹配对应的方法并执行。参考资料
🗑️
最近看了《反叛的鲁路修》,这部作品在很早之前就听说过,但曾经看过一次之后,发现机甲打斗蛮多,就没什么兴致看下去。
这么多年后,这次忍着看了下去,发现剧情还是蛮好的。动作摆起来感觉有点 JOJO 的风味。
The text was updated successfully, but these errors were encountered: