Redux-Saga: 核心原理剖析

剖析Saga底层原理: 手写Saga核心

Saga简介: (来自Saga文档介绍)redux-saga 是一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的 library,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易。
可以想像为,一个 saga 就像是应用程序中一个单独的线程,它独自负责处理副作用。 redux-saga 是一个 redux 中间件,意味着这个线程可以通过正常的 redux action 从主应用程序启动,暂停和取消,它能访问完整的 redux state,也可以 dispatch redux action。
redux-saga 使用了 ES6 的 Generator 功能,让异步的流程更易于读取,写入和测试。(如果你还不熟悉的话,这里有一些介绍性的链接) 通过这样的方式,这些异步的流程看起来就像是标准同步的 Javascript 代码。(有点像 async/await,但 Generator 还有一些更棒而且我们也需要的功能)。

  • 如上简介所示,在深究Saga之前,希望大家有Es6 Generator函数的基础,否则Saga的原理可能会让你看起来很疑惑~~
  • 在此之前,如果对Saga还不够了解可以前往Saga的文档进行基本的Saga使用了解: https://redux-saga-in-chinese.js.org/

  1. 首先,saga作为redux的一个中间件,所以需要具有redux中间件的基本结构(也就是多层嵌套的函数~~这样说可能比较唐突)
  2. Saga的实现主要是对Saga中内定的部分核心Effect副作用进行实现,这些Effect来自Saga下的一个effects包
  3. 从需求出发,实现Saga核心(也就是先根据原本的Saga库写个例子,然后根据这个例子开始实现相应的内容)
// store/index.js
// 定义redux store,添加saga中间件
import { applyMiddleware, createStore } from "redux"
import reducer from "./reducer"
import createSagaMiddleware from "../redux-saga"
import rootSaga from "./rootSaga"
let sagaMiddleware = createSagaMiddleware();
let store = applyMiddleware(sagaMiddleware)(createStore)(reducer);
sagaMiddleware.run(rootSaga)
export default store;

// store/reducer.js
// 定义redux reducer处理
import * as types from "./types"
export default function (state = {number: 0}, action) {  // 这里主要实现对number 加一操作
    switch(action.type){
        case types.INCREMENT: 
            return {number: state.number + 1};
        default:
            return state;
    }
}

// store/types.js
// 定义actionType类型文件
export const INCREMENT = 'INCREMENT';
export const ASYNC_INCREMENT = "ASYNC_INCREMENT";

// store/action.js
// 定义action
import * as types from "./types"
export default {
    asyncIncrement(){
        return { type: types.ASYNC_INCREMENT }  // 发起异步加一操作的action
    }
}

// store/rootSaga/index.js
// saga的根处理文件,最所有的saga处理进行整合
import * as types from "../types"
import { take, put, delay} from "../../redux-saga/effects"

export default function* rootSaga(){
    for(let i = 0; i < 3; i++){
        yield take(types.ASYNC_INCREMENT);
        delay(1000)
        yield put({ type: types.INCREMENT });
     }
}

实现Saga核心: (这里为了减少篇幅,之间写出最终的核心代码,里面附上详细注释,大家根据注释进行解读)

// redux-saga 核心部分:

// 这个库用于进行一些事件的绑定监听,例如take等Effect的处理
let EventEmitter = require("events")
// 作为redux的中间件, 应该具有的基本结构
export default function createSagaMiddleware(){
    let events = new EventEmitter()
    function sagaMiddleware({ getState, dispatch }){
        function run(generator){
            
            // 使run方法同时支持迭代器和generator参数:
            // 如果是一个generator函数则需要执行才能得到的iterator对象
            // 如果非函数相当于直接就是迭代器,可以直接进行迭代
            let it = typeof generator === "function" ? generator() : generator;
            function next(action){
                // 这里 next()执行之后得到的是generator中yield执行saga effect得到的结果值(基本都是些内置的Action, 和下面的switch中可以分别匹配)
                // 如执行take得到一个take相关的action, put执行得到一个put相关的action
                // 这里将action传递给next,将会作为 yield 的返回值,使得在yield之后可以得到异步执行的结果, 例如异步请求的数据等等
                let { value: effect, done } = it.next(action); 
                if(!done){
                    // 支持itarator处理: (关于Es6迭代器的内容这里不做介绍)
                    if(typeof effect[Symbol.iterator] == "function"){  // 包含Symbol.iaerator属性且是一个函数,则说明是一个迭代器
                        run(effect);  // 调用run方法进行递归
                        next();
                    } else if(effect.then){  // 如果effect是一个Promise, 对Promise进行支持
                        effect.then(next);  // 等待promise的成功后执行next
                    } else {
                        switch(effect.type){   // 关于这里swatch匹配后effect中的属性内容大家可以结合后面的effects查看
                            case 'TAKE':
                                events.once(effect.actionType, next);  // 使用events注册事件,若是take到对应的actionType来到执行next继续往下
                                break;
                            case 'PUT':
                                dispatch(effect.action);  // put就直接派发action
                                next();  // 递归,继续往下进行操作搜集
                                break;
                            case 'CALL':
                                let {fn, args, context} = effect.payload;
                                // 这里得到的是从delay函数中解析出的payload
                                // 其中得到的fn函数返回的是一个promise
                                fn.apply(context, args).then(next);
                                break;
                            case 'FORK':
                                run(effect.task);
                                next();  // 这里为了不阻塞进程的运行,立即调用next()
                                break;
                            case "CPS":
                                // cps中执行异步的回调函数是这里的next, 在异步执行结束之后继续往下执行,使得整个流程串联起来
                                effect.fn(...effect.args, (error, value) => {
                                    if(error){  // 如果error有值,则说明发生了错误,直接结束了当前的saga
                                        it.return('发生错位..')
                                    }
                                    next(value)
                                });
                                break;
                            default:
                                break;
                        }
                    }
                }
            }
            next();
        }
        sagaMiddleware.run = run;  // 将sagaMiddleware上挂载run方法,这个方法在定义Saga的时候调用,传入generator
        return function(next){   // 这里是redux中间件基本结构
            return function(action){
                events.emit(action.type, action)  // 这里监听actionType, 去出发相应的events
                next(action)
            }
        }
    }
	// 最终导出sagaMiddleware函数
    return sagaMiddleware;
}

effects相关:

// 定义saga相关的effect
export function take(actionType){
    return {
        type: "TAKE",
        actionType
    }
}
// put: 将会直接派发一个action
export function put(action) {
    return {
        type: 'PUT',
        action
    }
}

// call方法的第一个参数可以传递一个数组,数组的第一个元素为函数执行的上下文: context
// 第二个位置的内容为function
export function call(fn, ...args){
    let context = null;
    if(Array.isArray(fn)){
        context = fn[0];
        fn = fn[1];
    }
    return {
        type: "CALL",
        payload: { fn, context, args: args }
    }
}

export function delay(...args){
    return {
        type: "CALL",
        payload: {fn: delayP, context: null, args: args}
    }
}

// 这里得到的就是delay中args中的参数数组,通过apply方法执行传递过来
// args数组中的参数第一个元素就是延时的时间,之后可以传递其他的参数
function delayP(ms, val){
    return new Promise(function(resolve){
        setTimeout(function(){  //这里使用定时器达到延时效果
            resolve(val)
        }, ms)
    })
}

// 这里fork得到的task任务也是一个generator
export function fork(task){
    return {
        type: 'FORK',
        task
    }
}

// 这里类似于开启了一个新的进程,然后在进程里面进行死循环 然后执行take itarator (具体的实现是generator的嵌套执行)
// 这个方法执行后会得到一个generator的itarator的迭代器
export function* takeEvery(actionType, task){
    // 这里相当于 yield fork(saga), 也就是Generator嵌套Generator
    yield fork(function*(){
        while(true){
            yield take(actionType);
            yield task();
        }
    })
}

export function cps(fn, ...args){
    return {
        type: "CPS",
        fn,
        args
    }
}
  • 注: 这里为了简便就直接给出了最终写出的核心代码,大家可以将代码拷贝到IDE中,放到项目中进行测试解读,效果应该会更好哈~~
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值