🎂 什么是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(待续)
// 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')。
🎂 什么是redux-saga
简单的说是借助redux middleware, emitter(subscribe订阅/emit触发), web worker等来控制generators执行过程的一套机制。
🎉 什么是generator
🕯️ redux-saga的基本用法
🤡 从源码角度解读redux-saga(待续)
整个流程可以分两个方向
2, 点击获取用户信息的按钮(onFetchUser={() => action('FETCH_USER')}),因为我们加入了saga中间件,让我们发起store.dispatch({ type: FETCH_USER })的时候会处理异步操作, 也就是sagaEmitter.emit(action)