手写Redux-Saga源码 (4)

可以看到digestEffect又调用了一个函数runEffect,这个函数会处理具体的effect:

// runEffect就只是获取对应type的处理函数,然后拿来处理当前effect function runEffect(effect, currCb) { if (effect && effect.IO) { const effectRunner = effectRunnerMap[effect.type] effectRunner(env, effect.payload, currCb); } else { currCb(); } }

这点代码可以看出,runEffect也只是对effect进行了检测,通过他的类型获取对应的处理函数,然后进行处理,我这里代码简化了,只支持IO这种effect,官方源码中还支持promise和iterator,具体的可以看看他的源码:https://github.com/redux-saga/redux-saga/blob/master/packages/core/src/internal/proc.js

effectRunner

effectRunner是通过effect.type匹配出来的具体的effect的处理函数,我们先来看两个:take和fork。

runTakeEffect

take的处理其实很简单,就是将它注册到我们的channel里面就行,所以我们建一个effectRunnerMap.js文件,在里面添加take的处理函数runTakeEffect:

// effectRunnerMap.js function runTakeEffect(env, { channel = env.channel, pattern }, cb) { const matcher = input => input.type === pattern; // 注意channel.take的第二个参数是matcher // 我们直接写一个简单的matcher,就是输入类型必须跟pattern一样才行 // 这里的pattern就是我们经常用的action名字,比如FETCH_USER_INFO // Redux-Saga不仅仅支持这种字符串,还支持多种形式,也可以自定义matcher来解析 channel.take(cb, matcher); } const effectRunnerMap = { 'TAKE': runTakeEffect, }; export default effectRunnerMap;

注意上面代码channel.take(cb, matcher);里面的cb,这个cb其实就是我们迭代器的next,也就是说take的回调是迭代器继续执行,也就是继续执行下面的代码。也就是说,当你这样写时:

yield take("SOME_ACTION"); yield fork(saga);

当运行到yield take("SOME_ACTION");这行代码时,整个迭代器都阻塞了,不会再往下运行。除非你触发了SOME_ACTION,这时候会把SOME_ACTION的回调拿出来执行,这个回调就是迭代器的next,所以就可以继续执行下面这行代码了yield fork(saga)。

runForkEffect

我们前面的示例代码其实没有直接用到fork这个API,但是用到了takeEvery,takeEvery其实是组合take和fork来实现的,所以我们先来看看fork。fork的使用跟call很像,也是可以直接调用传进来的方法,只是call会等待结果回来才进行下一步,fork不会阻塞这个过程,而是当前结果没回来也会直接运行下一步:

fork(fn, ...args);

所以当我们拿到fork的时候,处理起来也很简单,直接调用proc处理fn就行了,fn应该是一个Generator函数。

function runForkEffect(env, { fn }, cb) { const taskIterator = fn(); // 运行fn得到一个迭代器 proc(env, taskIterator); // 直接将taskIterator给proc处理 cb(); // 直接调用cb,不需要等待proc的结果 } runPutEffect

我们前面的例子还用到了put这个effect,他就更简单了,只是发出一个action,事实上他也是调用的Redux的dispatch来发出action:

function runPutEffect(env, { action }, cb) { const result = env.dispatch(action); // 直接dispatch(action) cb(result); }

注意我们这里的代码只需要dispatch(action)就行了,不需要再手动调channel.put了,因为我们前面的中间件里面已经改造了dispatch方法了,每次dispatch的时候都会自动调用channel.put。

runCallEffect

前面我们发起API请求还用到了call,一般我们使用axios这种库返回的都是一个promise,所以我们这里写一种支持promise的情况,当然普通同步函数肯定也是支持的:

function runCallEffect(env, { fn, args }, cb) { const result = fn.apply(null, args); if (isPromise(result)) { return result .then(data => cb(data)) .catch(error => cb(error, true)); } cb(result); }

这些effect具体处理的方法对应的源码都在这个文件里面:https://github.com/redux-saga/redux-saga/blob/master/packages/core/src/internal/effectRunnerMap.js

effects

上面我们讲了几个effect具体处理的方法,但是这些都不是对外暴露的effect API。真正对外暴露的effect API还需要单独写,他们其实都很简单,都是返回一个带有type的简单对象就行:

const makeEffect = (type, payload) => ({ IO: true, type, payload }) export function take(pattern) { return makeEffect('TAKE', { pattern }) } export function fork(fn) { return makeEffect('FORK', { fn }) } export function call(fn, ...args) { return makeEffect('CALL', { fn, args }) } export function put(action) { return makeEffect('PUT', { action }) }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsspgd.html