Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

关于redux-saga #3

Open
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(待续)

// 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
 }
 }
//...省略

整个流程可以分两个方向

  1. 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

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

        AltStyle によって変換されたページ (->オリジナル) /