redux-saga
redux-sage
sagaAPI: https://redux-saga-in-chinese.js.org/docs/api/
- 纯净
- 强大
- 灵活
在saga任务中,如果yield了一个普通数据,saga不作任何处理,仅仅将数据传递给yield表达式(把得到的数据放到next的参数中),因此,在saga中,yield一个普通数据没什么意义。
saga需要你在yield后面放上一些合适的saga指令(saga effects), 如果放的是指令,saga中间件会根据不同的指令进行特殊处理。以控制整个任务的流程
每个指令本质上就是一个函数,该函数调用后,会返回一个指令对象,saga会接收到对该指令对象,进行各种处理
一旦saga任务完成(生成器函数),则saga中间件一定结束
-
take指令: 【阻塞】监听某个action,如果action发生了,则会进行下一步处理,take指令仅监听一次。yield得到一个完美的对象。
-
all指令: 【阻塞】该函数传入一个数组,数组中放入生成器,saga会等待所有的生成器全部完成后才会进一步处理
-
takeEvery指令: 不断的监听某个action, 当某个action到达之后,运行一个函数。takeEvery永远不会结束当前的生成器
-
delay指令:【阻塞】 阻塞指定的毫秒数
-
put指令: 用于重新触发action, 相当于dispatch一个action
-
call指令: 【可能阻塞】 用于副作用的(通常用于异步)函数调用
-
apply指令: 与call类似
-
select指令: 用于得到当前仓库的数据
-
cps指令: 【可能阻塞】用于调用那些传统的回调方法的异步函数
-
fock指令: 用于开启几个新的任务,该任务不会阻塞,该函数需要传递一个生成器函数,fork返回一个对象,类型为task, 创建一个 Effect 描述信息,用来命令 middleware 以 非阻塞调用 的形式执行 fn。
-
cancel指令:用于取消一个或多个任务,实际上,取消的实现原理,是利用generator。return 。 cancel可以不传递参数,取消自己 创建一个 Effect 描述信息,用来命令 middleware 取消之前的多个分叉任务。
-
takeLast俄速通: 功能和takeEvery一致,只不过,会自动取消掉之前开启的任务
-
cancelled指令:判断担起任务线是否被取消掉了
-
race指令:【竞赛】 可以传递多个指令,当其中一个指令结束后,会直接结束,与promise。rece类似。
手写saga
saga中间件的原理:
- 首先启动一个任务
- 当action触发时,直接将action分发到下一个中间件
源码分析:
- runSaga:一个函数,用于启动一个任务,一个任务的本质是一个generator
function,runSaga在内部得到该函数的generator,并控制生成器的每一次迭代。
redux-saga
index
import runSaga from './runSaga'
/**
* saga 中间件的函数
*/
export default function() {
return function sagaMiddleWare(store) {
const env = {
store
}
sagaMiddleWare.run = runSaga.bind(null, env)
return function (next) {
return function(action){
return next(action); //直接交给下一个中间件
}
}
}
}
runSaga.js
import { Task } from "./Task";
import isGenerator from 'is-generator'
// import from 'is-promise'
import { isEffect } from './effectHelper'
import isPromise from "is-promise";
import runEffect from './runEffect'
/**
* 开启一个新函数
* @param {*} env 全局环境的数据,被saga执行期间
* @param {*} generatorFunc 生成器函数
* @param {*} args 生成器函数的参数
*/
export default function(env, generatorFunc, ...args) {
console.log("一个新的任务启动了");
const iterator = generatorFunc(...args)
if (isGenerator(iterator)){
//不断调用next, 直到迭代结束
next();
}else{
console.log("一个普通函数");
}
/**
*
* @param {*} nextvalue 正常调用iterator.next时传递的值
* @param {*} err 错误对象
* @param {*} isOver 是否结束
*/
function next(nextvalue, err, isOver){
let result; // 记录迭代的结果{value: xxx , done: false }
if(err) {
//情况2: 调用iterator.throw(err)
iterator.throw(err)
}else if(isOver) {
//情况3: 调用iterator.return
result = iterator.return();//结束迭代
}else{
//情况1: 调用iterator.next(value)
result = iterator.next(nextvalue)
}
const { value, done } = result
if(done) {
//迭代器结束
return;
}
//没有结束
if(isEffect(value)){
console.log("是一个effect");
runEffect(env, value, next)
}else if(isPromise(value)) {
//不是一个 effect
// 情况1:value是promisi
console.log("不是一个effect,是promise");
value.then(r=>next(r))
.catch(err => next(null, err))
}else{
//情况2:其他
console.log("不是一个effect是其他");
next(value); //直接下一步
}
}
return new Task()
}
effectHelper.js
//该模块为创建effect和判断effect提供支持
/**
* effect 的可用类型
*/
export const effectTypes = {
CALl: "CALL",
TAKE: "TAKE",
FORK: "FORK",
ALL: "ALL",
PUT: "PUT"
}
/**
*
* @param {*} type 有效的effect类型
* @param {*} payload
*/
export function createEffect(type, payload){
// 验证 type
if (!Object.values(effectTypes).includes(type)){
throw new TypeError("这是无效的type值")
}
return {
"@@redux-saga/IO": true,
type,
payload
}
}
/**
* 判断一个对象是否是effect
* @param {*} obj
*/
export function isEffect(obj) {
if(typeof obj !== "object") {
return false;
}
if(obj["@@redux-saga/IO"] === true){
return true
}
return false
}
runEffect.js
import { effectTypes } from 'redux-saga/effects';
import effctHelper from './effectHelper';
import {runCallEffect} from './effects/call'
//该模块主要用于处理一个effect对象
/**
* 处理一个effect对象 根据不同的effect。type值进行不同的处理
* @param {*} env 全局的环境对象
* @param {*} value effect对象
* @param {*} next 下一个处理
*/
export default function (env, effect, next) {
switch (effctHelper.type){
case effectTypes.CALL:
runCallEffect(env, effect, next)
break;
default:
throw new Error("类型无效");
}
}
redux-saga\effects
index.js
export { call } from './call'
export { delay } from './delay'
export { pull } from './pull'
call.js
import { createEffect, effectTypes } from '../effectHelper';
import isPromise from 'is-promise'
//1. 提供一个call函数用于产生call effect
export function call(fn, ...args){
var context = null , //this的指向
func = fn; //要运行的函数
if(Array.isArray(fn)){
context = fn[0]; //this的指向数组的第一项
func = fn[1]; //运行函数指向数组的第二项
}
return createEffect(effectTypes.CALl, {
context,
fn: func,
args
})
}
//2. 处理call effect
export function runCallEffect(env, effect, next) {
const { context, fn, args} = effect.payload
const result = fn.call(context,...args)
if(isPromise(result)){
result.then(v=>next(v))
.catch(err => next(null, err))
}
else{
next(result)
}
}
delay.js
import { call } from './call'
export function delay(duration) {
return call(function(){
return new Promise(resolve => {
setTimeout(() => {
resolve();
}, duration);
})
})
}
pull.js
import { createEffect, effectTypes } from '../effectHelper'
export function put(action) {
return createEffect(effectTypes.PUT, {
action
})
}
export function runPutEffect(env, effect, next) {
const action = effect.payload.action;
const result = env.store.dispatch(action)
next(result)
}