React开发进阶redux saga使用原理详解

前言

工作中使用了redux-saga这个redux中间件,如果不明白内部原理使用起来会让人摸不着头脑,阅读源码后特意对其原理做下总结。

redux的特点

  • 一个标准、管理应用副作用的redux中间件
  • 实现切面编程方式
  • 声明式的编写方式

订阅发布的设计模式

优点:

  • 把异步操作转移到单独 saga文件中,而不是糅杂在action或者component中;
  • dispatch的参数保持为纯粹的action而不是thunk function;
  • 大量的saga辅助函数和effect创建器减少了开发者的开发成本;
  • 灵活的串行或并行能够实现复杂的异步流程。

分析原理

先举个实践的例子

import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import thunk from 'redux-thunk';
import { put, takeEvery, delay, call, select } from 'redux-saga/effects';
const reducer = (state = 0, action) => {
 switch (action.type) {
 case 'put':
 return state + action.payload;
 default:
 return state;
 }
};
const sagaMiddleware = createSagaMiddleware()
export const store = createStore(reducer, applyMiddleware(sagaMiddleware, thunk));
function* main() {
 // 工具函数 delay 阻塞1s
 var start = Date.now();
 yield delay(1000);
 console.log(Date.now() - start);// 1秒多
 // put 类似于 dispatch
 yield put({ type: 'put' , payload:10});
 // takeEvery 不阻塞程序
 yield takeEvery('takeEvery111', function ({ type, payload }) {
 console.log('takeEvery', type, payload); // yield put({ type: 'takeEvery111' , payload:10}); 触发
 });
 // select 获取state中的数据
 const state = yield select((state) => state);
 console.log(state); // 10
 // call 阻塞程序
 yield call(function* () { // 阻塞
 yield delay(1000);
 });
 console.log(Date.now() - start);// 2秒多
 yield put({ type: 'takeEvery111' , payload:10});
}
sagaMiddleware.run(main);

依次打印出如下结果:

1001
10
2004
takeEvery takeEvery111 10

1. 自动执行Generator

从执行结果来看,这个main函数能自动按顺序执行说明在redux-saga的程序代码中有自动执行gen的机制,其实源码就是./internal/proc.js文件中,通过函数之间循环调用的方式执行这个gen函数。

这样的自动执行机制在generator中是比较常见的,比如co模块就具有这样的功能,其实现巧妙却不复杂,如下例子:

function makePromisify(source) {
 if (source.then && typeof source.then === "function") return source
 return Promise.resolve(source)
}
function run(generatorFunc) {
 let it = generatorFunc()
 let result = it.next()
 return new Promise((resolve, reject) => {
 const next = function (result) {
 if (result.done) {
 resolve(result.value)
 }
 //保证返回的是一个promise
 result.value = makePromisify(result.value)
 result.value.then(res => {
 //将promise的返回值res传入iterator迭代器的next方法中,作为yield后面表达式的返回值
 //it.next将停止的yield继续执行到下一个yield,返回的result是一个value,done属性组成的对象
 let result = it.next(res)
 //递归执行next函数
 next(result)
 }).catch(err => {
 reject(err)
 })
 }
 next(result)
 })
}

2. 发布订阅模式

我们看到takeEvery是可以拦截到{type: 'takeEvery111'}这个action,说明在redux-saga内部有类似发布订阅on/trigger这样的机制,通过阅读源码我们发现,内部通过channel这个东西来做发布订阅的处理;在./internal/channel.js就有这样的源码:

put(input) {
 const takers = (currentTakers = nextTakers)
 for (let i = 0, len = takers.length; i < len; i++) {
 const taker = takers[i]
 // 如果take匹配到, 执行它
 if (taker[MATCH](input)) {
 taker.cancel()
 taker(input) // 发布
 }
 }
},
take(cb, matcher = matchers.wildcard) {
 cb[MATCH] = matcher
 ensureCanMutateNextTakers()
 nextTakers.push(cb) // 订阅
},

3. put, takeEvery, delay, call返回effect

put执行并不是直接dispatch一个action,而是通过yield向redux-saga内部传递参数(这个参数在redux-saga中叫effect),内部根据这个参数决定具体的执行内容。

在源码中put、fork、call 等是通过 makeEffect 创建了一系列 effect,这个 effect 是一个普通的 js 对象,上面挂载了一些相关的信息,并且把这个effect yield到内部的runEffejct中,然后根据type字段决定真正需要执行的程序过程。

const makeEffect = (type, payload) => ({
 [IO]: true,
 combinator: false,
 type,
 payload,
});

总结

作者:空山与新雨原文地址:https://www.cnblogs.com/walkermag/p/16913838.html

%s 个评论

要回复文章请先登录注册