前言
在react开发实践中,由于过多复杂异步交互逻辑经常发生,使用redux-thunk,react-promise,async await等方案实现异步交互,始终无法完美适配应用各种场景。于是redux-saga出生了,基于generator的原理实现的全部异步流程。
saga的使用
- 调用createSagaMiddleware创建saga中间件函数
- 使用applyMiddleware将中间件传入,此处主要是为了获取redux中的数据操作权力。
- 启动saga函数,sagaMiddleware.run(rootSaga)
import { createStore, applyMiddleware } from "redux";
import createSagaMiddleware from "../redux-saga";
import { getPayload, rootSaga } from "./saga"; // 拿到定义好的saga
const sagaMiddleware = createSagaMiddleware();
// 创建一个计数器
function createCounter({count = 0} = {}, action) {
switch (action.type) {
case "sagaPayload": { // 使用saga进行异步
console.log('sagaPayload action',action);
// return state + 1;
return {
count: count + 1,
payload: action.payload
}
}
default: return { count: count, payload: [] };
}
}
// 将数据源导出使用
const store = createStore(
createCounter,
// applyMiddleware(sagaMiddleware, reduxThunk, reduxPromise, reduxLogger)
applyMiddleware(sagaMiddleware)
);
sagaMiddleware.run(rootSaga); // 将想使用的saga注入
export default store;
createSagaMiddleware
创建saga中间件,并执行初始化,将回调与key进行关联
import { stdChannel } from "./channel";
import runSaga from "./runSaga";
export default function createSagaMiddleware() {
let boundRunSaga;
let channel = stdChannel(); // 拿到数据存储区域,用于公用数据存储
function sagaMiddleware({ getState, dispatch }) {
boundRunSaga = runSaga.bind(null, { channel, getState, dispatch }); // 使用bind传入参数,并返回新函数可供调用, 传入存储结构 channel
return next => action => {
let result = next(action);
channel.put(action); // 当用户调用时(触发dispatch),执行下一个generator的next
return result
}
}
// run接收的就是实际的saga函数(generator), 使用展开符,否则接收的是个数组
sagaMiddleware.run = (...args) => boundRunSaga(...args);
// 返回的这个函数用于连接redux。
return sagaMiddleware;
}
stdChannel
import { MATCH } from "./symbols";
// 额外开一个区域存储数据
export function stdChannel() {
const currentTakes = [];
function take(cb, matcher) {
cb[MATCH] = matcher; // 创建关联关系,以便将来查找
currentTakes.push(cb);
}
function put(input) {
const takers = currentTakes;
for(let i=0, len = takers.length; i<len; i++) {
const taker = takers[i];
if (taker[MATCH](input)) { // 这里这个函数是effectRunnerMap中定义的 matcher
taker(input); // 这里是 take 接收的cb
}
}
}
return {
take,
put
}
}
runSaga
因为saga是个generator函数,所有要先触发,拿到迭代器对象。
import proc from "./proc";
export default function runSaga({channel, getState, dispatch }, saga, ...args) {
const iterator = saga(...args); // 拿到迭代器对象
const env = { channel, getState, dispatch }; // 传入需要的数据,以便将来使用
proc(env, iterator); // 执行遍历器
}
proc
执行遍历器,直至generator对象的 done 属性为 true 这里是获取用户使用 call fork put 这些generator函数执行的时刻 会拿到这些函数的返回值,res.value,并进行一系列操作。
import effectRunnerMap from "./effectRunnerMap";
import { IO } from "./symbols";
export default function proc(env, iterator, cb) {
// console.log("proc", env, iterator);
next(); // 执行遍历器next
function next(arg, isErr) {
let res;
if (isErr) {
res = iterator.throw(arg);
} else {
res = iterator.next(arg);
}
// res => {value:, done: false/true}
if (!res.done) {
digestEffect(res.value, next);
} else {
if (typeof cb === "function") cb(res);
}
}
function digestEffect(effect, cb) {
// 标记是否结束,且状态不可变 类似 promise 中的状态;
let effectSettled;
function currCb(res, isErr) {
if (effectSettled) return;
effectSettled = true;
cb(res, isErr)
}
runEffect(effect, currCb);
}
function runEffect(effect, currCb) {
// 判断是否是effect对象
// console.log("runEffect", effect);
if (effect && effect[IO]) {
const effectRunner = effectRunnerMap[effect.type];
effectRunner(env, effect.payload, currCb);
} else {
currCb(); // 如果不是直接执行即可
}
}
}
effectRunnerMap
这里是真正执行对应操作的函数,runTakeEffect是收集 key 匹配的回调,将来在更新dispatch时,会触发收集的回调函数。
import effectTypes from "./effectTypes";
import proc from "./proc";
// 返回对应类型需要执行的函数
const effectRunnerMap = {
[effectTypes.TAKE]: runTakeEffect,
[effectTypes.PUT]: runPutEffect,
[effectTypes.CALL]: runCallEffect,
[effectTypes.FORK]: runForkEffect,
[effectTypes.ALL]: runAllEffect,
}
// 给用户暴露 channel 参数,可自定义传入、
function runTakeEffect(env, {channel = env.channel, pattern}, cb) {
// console.log("runTakeEffect", arguments);
const matcher = input => input.type === pattern;
channel.take(cb, matcher);
}
function runPutEffect(env, {action}, cb) {
// console.log("runPutEffect", arguments);
const res = env.dispatch(action);
cb(res);
}
function runCallEffect(env, {fn, args}, cb) {
// console.log("runCallEffect", arguments);
const res = fn.apply(null, args);
if (isPromise(res)) { // fn是promise
res
.then(data => cb(data))
.catch(err => cb(err, true))
} else if (isGenerator(res)) {
proc(env, res, cb);
} else {
cb(res);
}
}
function runForkEffect(env, {fn, args}, cb) {
// console.log("runForkEffect", arguments);
// fn 是generator
const taskIterator = fn.apply(null, args); // args是一个数组,需要展开
proc(env, taskIterator);
cb(); // 下一个next
}
function runAllEffect(env, effects, cb) {
const length = effects.length;
for (let i = 0; i < length; i++) {
proc(env, effects[i])
}
}
function isPromise(fn) {
return typeof fn.then === "function"
}
function isGenerator(obj) {
return typeof obj.next === "function"
}
export default effectRunnerMap;
effects
真正暴露给用户使用的函数,用于获取副作用,将传入的key,参数,回调等一一建立联系,以便将来执行时一一匹配。
import effectTypes from "./effectTypes";
import { IO } from "./symbols";
// 标记effect对象
const makeEffect = (type, payload) => {
return { [IO]: IO, type, payload }
}
// 以下函数,因为是generator对象,所以在迭代器执行过程中能够获取makeEffect()的返回值,虽然并没有显式返回。
// yield call() => iterator.value(在这里获取)
// 代码中在 proc.js 下 digestEffect 能够清楚看见。
export function take(pattern) {
// console.log("pattern", pattern);
return makeEffect(effectTypes.TAKE, {pattern})
}
export function put(action) {
return makeEffect(effectTypes.PUT, {action})
}
export function call(fn, ...args) {
return makeEffect(effectTypes.CALL, { fn, args })
}
export function fork(fn, ...args) {
return makeEffect(effectTypes.FORK, { fn, ...args })
}
export function all(effects) {
return makeEffect(effectTypes.ALL, effects)
}
创建使用函数
这是使用层,定义异步逻辑
import { all, call, put, take, fork } from "../redux-saga/effects";
// worker 真正的工作函数
export function* getPayloadUseSaga() {
const res = yield call(getOriginPayload); // 调用层 拿到结果
// console.log('res', res);
yield put({ type: "sagaPayload", payload: res.data}) // 通知层,通知redux该更新数据了
}
// watcher 函数,连接对应 key 和回调的关系
export function* getPayload() {
while (true) {
const action = yield take("getSagaPayload");
console.log("action getSagaPayload", action);
yield fork(getPayloadUseSaga, action)
}
}
const getOriginPayload = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ type: "origin", data: [1, 2, 3, 4] })
}, 1000);
})
}
// 点击事件。发送对应key的事件响应
export const handle = (payload) => {
console.log('payload', payload);
return { type: "getSagaPayload", payload }
}
// all 执行多个saga
export function* rootSaga() {
yield all([getPayload()])
}
组件调用
import React, { } from 'react';
import { connect } from "react-redux";
import { handle } from '../store/saga';
const Index = (props) => {
const sagaPayload = () => {
console.log("点击");
props.handle(123)
}
return (
<div>
<h1>首页</h1>
<h3>count: {props.count}</h3>
<ul>
{props.payload.map(v => {
return <li key={v}>{v}</li>
})}
</ul>
<p>
<button onClick={sagaPayload}>saga获取数据</button>
</p>
</div>
)
}
// 拿到state。并决定哪些值要映射到index的props上
const mapStateToProps = function(state) {
console.log("state", state);
return state;
}
// 映射dispatch action,减少手动调用dispatch的步骤
const mapDispatchToPorps = {
handle
}
// 使用react-redux connect返回一个新的组件。对其进行props的额外添加和数据监听等
const WrappedIndex = connect(
mapStateToProps,
mapDispatchToPorps
)(Index);
export default WrappedIndex;
结语
以上便实现了redux-saga的大部分功能,处理逻辑确实有些复杂,需要多看几遍才能理解。其中的核心思路是通过yield"暂停"函数,使用iterator.value"隔空(非显式)"获取函数的返回值,从而进行逻辑处理。github代码