-
Notifications
You must be signed in to change notification settings - Fork 0
Open
@Kehao
Description
🎂 什么是redux-saga
简单的说是借助redux middleware, emitter(subscribe订阅/emit触发), web worker等来控制generators执行过程的一套机制。
🎉 什么是generator
function* helloWorld() { yield 'hello'; yield 'world'; return 'end'; } var hw = helloWorld(); // 执行结果 hw.next() // { value: 'hello', done: false } hw.next() // { value: 'world', done: false } hw.next() // { value: 'end', done: true } hw.next() // { value: undefined, done: true }
🕯️ redux-saga的基本用法
// 创建saga ./saga.js export function* rootSaga() { while(true) { yield take('FETCH_USER'); const { data } = yield call(axios.post, 'http://localhost:8002/users'); yield put({ type: 'UPDATE_USER', payload: data }) } }
// 入口文件 ./index.js import { put, call, take } from 'redux-saga/effects'; import { createStore, applyMiddleware } from 'redux'; import createSagaMiddleware from 'redux-saga'; import rootSaga from './rootSaga' // 创建action const action = type => store.dispatch({ type }) // 创建redux-saga中间件 const sagaMiddleware = createSagaMiddleware() // 生成store const store = createStore(rootReducer, applyMiddleware(sagaMiddleware)) // 执行redux-saga中间件 sagaMiddleware.run(rootSaga) function render() { ReactDOM.render( <App users={store.getState().users} onFetchUser={() => action('FETCH_USER')} />, document.getElementById('root'), ) }
🤡 从源码角度解读redux-saga(待续)
- 代码参考版本: v0.16.2
// redux-saga/src/internal/middleware.js //...省略 function sagaMiddleware({ getState, dispatch }) { const sagaEmitter = emitter() sagaEmitter.emit = (options.emitter || ident)(sagaEmitter.emit) // 1, 初始化的时候会调用sagaMiddleware.run(rootSaga),主要功能是执行一次generator并且如果是一个taker,则把next函数放入到channel的takers数组中。 // 2, 新建一个emitter,把调用该takers数组的句柄(channel.put(input))放入到该emmiter的subscribers数组中,当调用sagaEmitter.emit(action)时会遍历执行。 sagaMiddleware.run = runSaga.bind(null, { context, subscribe: sagaEmitter.subscribe, dispatch, getState, sagaMonitor, logger, onError, }) return next => action => { if (sagaMonitor && sagaMonitor.actionDispatched) { sagaMonitor.actionDispatched(action) } // 先触发reducer,然后才再处理action,所以effect要慢于reducer const result = next(action) // hit reducers // 遍历subscribers数组,执行和action匹配的taker sagaEmitter.emit(action) return result } } //...省略
整个流程可以分两个方向
- saga初始化
// redux-saga/src/internal/middleware.js
sagaMiddleware.run(rootSaga)
// redux-saga/src/internal/runSaga.js
//...省略
export function runSaga(storeInterface, saga, ...args) {
// proc是针对一个iterator的操作,iterator是对rootSaga这个generator函数的封装
const task = proc(
// iterator是对generator函数的封装
iterator,
subscribe,
wrapSagaDispatch(dispatch),
getState,
context,
{ sagaMonitor, logger, onError },
effectId,
saga.name,
)
//...省略
return task
}
// redux-saga/src/internal/proc.js //...省略 export default function proc(iterator) { // 执行这里会把获取管道中taker的方法(chan.put)push到subscribers,所以上面第一步执行订阅中的方法实际上是执行chan.put(input) var stdChannel = _stdChannel(subscribe); // 这里的task在第一次执行的时候直接返回 var task = newTask(parentEffectId, name, iterator, cont); // 执行gen.next,就会执行到我们的yield take('FETCH_USER'); 然后会返回value={TAKE:{pattern: "FETCH_USER"}},根据返回的值,判断会执行runTakeEffect()函数 next(); function next(arg, isErr) { var result = void 0; // iterator是封装后的rootSaga,这里执行到的是yield take('FETCH_USER'),返回value={TAKE:{pattern: "FETCH_USER"}} result = iterator.next(arg); // 根据返回的value,这里会执行runTakeEffect runEffect(result.value, parentEffectId, '', next); } } //...省略
export function eventChannel(subscribe, buffer = buffers.none(), matcher) { /** should be if(typeof matcher !== undefined) instead? see PR #273 for a background discussion **/ if (arguments.length > 2) { check(matcher, is.func, 'Invalid match function passed to eventChannel') } const chan = channel(buffer) const close = () => { if (!chan.__closed__) { if (unsubscribe) { unsubscribe() } chan.close() } } // 关键代码,把chan.put(input)放入subscribe数组中 const unsubscribe = subscribe(input => { if (isEnd(input)) { close() return } if (matcher && !matcher(input)) { return } chan.put(input) }) if (chan.__closed__) { unsubscribe() } if (!is.func(unsubscribe)) { throw new Error('in eventChannel: subscribe should return a function to unsubscribe') } return { take: chan.take, flush: chan.flush, close, } }
function runTakeEffect(_ref2, cb) { var channel = _ref2.channel, pattern = _ref2.pattern, maybe = _ref2.maybe; channel = channel || stdChannel; var takeCb = function takeCb(inp) { return cb(inp); }; // 给管道注册taker,把next函数放到takers数组中 channel.take(takeCb, matcher(pattern)); }
2, 点击获取用户信息的按钮(onFetchUser={() => action('FETCH_USER')}),因为我们加入了saga中间件,让我们发起store.dispatch({ type: FETCH_USER })的时候会处理异步操作, 也就是sagaEmitter.emit(action)
export function emitter() { // 略... // 执行所有订阅方法 // 这里会遍历订阅函数,并执行订阅函数里的方法。在初始化的是我们已经把获取管道中taker的方法push到订阅函数了,所以我们这里执行的是获取管道中的taker。 function emit(item) { var arr = subscribers.slice(); for (var i = 0, len = arr.length; i < len; i++) { arr[i](item); } } }
function put(input) {
// ...
for (var i = 0; i < takers.length; i++) {
var cb = takers[i];
if (!cb[MATCH] || cb[MATCH](input)) { //匹配action的逻辑
takers.splice(i, 1); // 删除已经执行过的taker
return cb(input); // 这里cb实际上是next
}
}
}
// 在初始化执行yield take('FETCH_USER')的时候,已经把gen.next放入到takers中,这里cb(input)实际上是执行gen.next({ type: FETCH_USER }), // 因为在初始化的时候gen函数已经执行了一次gen.next,现在执行gen.next则为const { data } = yield call(axios.post, 'http://localhost:8002/users'),同时把{ type: FETCH_USER }作为上一步的值传入。 // 执行yeild call返回value{CALL:{args: [url]}},根据返回值, 这里会执行 function runCallEffect(_ref4, effectId, cb) { const result = fn.apply(context, args); // 这里执行结果是promise return resolvePromise(result, cb); }
// 接下来gen.next执行到的是yield put({ type: 'UPDATE_USER', payload: data }),执行的返回值value // {PUT:{action:{payload:{id: "xx",type:"UPDATE_USER"}}}},根据返回值,会执行 function runPutEffect(_ref3, cb) { var channel = _ref3.channel, action = _ref3.action; asap(function () { var result = (channel ? channel.put : dispatch)(action); return cb(result); }); } // 执行dispatch(action)这里又会回到中间件中再次进入第三步的开始过程。并完成更新。 // 这次执行到遍历takers的地方,takers已经为空数组,会直接return,至此完成了整个获取接口到更新数据。 // 由于while(true)循环,再次执行yield take('FETCH_USER')。
Metadata
Metadata
Assignees
Labels
No labels