redux-saga的工作流程

242 阅读5分钟

redux-saga

官网:redux-saga.js.org/docs/About
中文网:redux-saga-in-chinese.js.org/

redux-saga 是一个用于管理 异步获取数据(副作用) 的redux中间件;它的目标是让副作用管理更容易,执行更高效,测试更简单,处理故障时更容易…

学习 redux-saga 之前,需要先掌握 ES6 中的 Iterator迭代器 和 Generator生成器 !!

1. redux-thunk与redux-saga的比较

redux中的数据流
action ———> reducer ———> state

  • action是一个纯粹对象(plain object)
  • reducer是一个纯函数(和外界没有任何关系)
  • 都只能处理同步的操作

redux-thunk中间件的处理流程
action1 ———> middleware ———> action2 ———> reducer ———> state

/* redux-thunk中间件的部分源码 */

'use strict';

function createThunkMiddleware(extraArgument) {

    var middleware = function middleware(_ref) {

      var dispatch = _ref.dispatch,

          getState = _ref.getState;

      return function (next) {

        return function (action) {

          if (typeof action === 'function') {

            // 如果返回的action是个函数,则把函数执行「在函数中完成异步操作,用传递的dispatch单独实现派发!!」

            return action(dispatch, getState, extraArgument);

          }

          return next(action);

        };

      };

    };

    return middleware;

}

弊端:异步操作分散到每一个action中;而且返回函数中的代码具备多样性!!

redux-saga中间件的工作流程
redux-saga中提供了一系列的api,可以去监听纯粹对象格式的action,方便单元测试!!
action1 ———> redux-saga监听 ———> 执行提供的API方法 ———> 返回描述对象 ———> 执行异步操作 ———> action2 ———> reducer ———> state

这样说完,大家可能是不太理解的,那么接下来,我们去做一个案例,详细解读一下redux-saga的语法和优势!!


2. redux-saga的基础知识

备注:需要先准备一套基于 “redux、redux-thunk” 实现的投票案例,我们在此基础上去修改 !!

安装中间件

$ npm install redux-saga  
$ yarn add redux-saga

使用中间件
store/index.js

import { createStore, applyMiddleware } from 'redux';

import createSagaMiddleware from 'redux-saga';

import reducer from './reducer';

import saga from './saga';

// saga

const sagaMiddleware = createSagaMiddleware();

// 创建store容器

const store = createStore(

    reducer,

    applyMiddleware(sagaMiddleware)

);

// 启动saga

sagaMiddleware.run(saga);

export default store;

store/action-types.js

export const VOTE_SUP = "VOTE_SUP";

export const VOTE_OPP = "VOTE_OPP";



export const DEMO = "DEMO";

store/reducer

// demoReducer

import * as TYPES from '../action-types';

import _ from '../../assets/utils';

let initial = {

    num: 0

};

export default function demoReducer(state = initial, action) {

    state = _.clone(state);

    let { payload = 1 } = action;

    switch (action.type) {

        case TYPES.DEMO:

            state.num += payload;

            break;

        default:

    }

    return state;

};



// index.js

import { combineReducers } from 'redux';

import voteReducer from './voteReducer';

import demoReducer from './demoReducer';

const reducer = combineReducers({

    vote: voteReducer,

    demo: demoReducer

});

export default reducer;

Demo.jsx组件中

import React from "react";

import { Button } from 'antd';

import { useSelector, useDispatch } from 'react-redux';

const Demo = function Demo() {

    const { num } = useSelector(state => state.demo),

        dispatch = useDispatch();

    return <div>

        <span style={{ fontSize: 20, paddingLeft: 10 }}>

            {num}

        </span>

        <br />

        <Button type="primary"

            onClick={() => {

                //基于dispatch进行派发....

            }}>

            按钮

        </Button>

    </div>;

};

export default Demo;

saga.js 工作流程 image.png

  • take(pattern)
  • takeEvery(pattern, saga, …args)
  • takeLatest(pattern, saga, ..args)
  • throttle(ms, pattern, saga, ..args)

第二部分:创建执行函数「基于Effect创建器(API)」

  • put(action)
  • call(fn, …args)
  • fork(fn, …args)
  • select(selector, …args)

每一次组件派发后发生的事情
每一次在组件中,基于 dispatch(action) 的时候:

  • 首先会通知 reducer 执行
  • 然后再去通知 saga 中的监听器执行

image.png

关于监听器创建的细节
组件中

<Button type="primary"

    onClick={() => {

        dispatch({

            type: "DEMO-SAGA",

            payload: 10

        });

    }}>

    按钮

</Button>

saga.js -> take 函数的运用

import * as TYPES from './action-types';

const working = function* working(action) {

    // 等价于 dispatch派发:通知reducer执行

    yield put({

        type: TYPES.DEMO,

        payload: action.payload

    });

};

export default function* saga() {

    /* // 创建监听器,当监听到派发后,才会继续向下执行

    // 特征:只能监听一次

    // action:可以获取派发时传递的action

    let action = yield take("DEMO-SAGA");

    yield working(action); */



    // 可基于循环,创建无限监听机制

    while (true) {

        let action = yield take("DEMO-SAGA");

        yield working(action);

    }

};

saga.js -> 其它监听器辅助函数的运用

const working = function* working(action) {

    console.log('AAA');

    // 设置延迟函数:等待2000ms后,才会继续向下执行!!

    yield delay(2000);

    yield put({

        type: TYPES.DEMO,

        payload: action.payload

    });

};

export default function* saga() {

    /* // 派发后,立即通知异步的working执行;

    // 但是在working没有处理完毕之前,所有其他的派发任务都不在处理!!

    while (true) {

        let action = yield take("DEMO-SAGA");

        yield working(action);

    } */



    /* // 每一次派发任务都会被执行

    yield takeEvery("DEMO-SAGA", working); */



    /* // 每一次派发任务都会被执行,但是会把之前没有处理完毕的干掉

    yield takeLatest("DEMO-SAGA", working); */



    /* // 每一次派发的任务会做节流处理;在频繁触发的操作中,1000ms内,只会处理一次派发任务

    yield throttle(1000, "DEMO-SAGA", working); */



    /* // 每一次派发的任务会做防抖处理;在频繁触发的操作中,只识别最后一次派发任务进行处理

    yield debounce(1000, "DEMO-SAGA", working); */

};

saga.js -> yield call/select…

import * as TYPES from './action-types';

import http from '../api/http';



const working = function* working() {

    // 获取目前的公共状态信息

    let { num } = yield select(state => state.demo);



    // 从服务器获取数据

    // let result = yield apply(null, http.get, ['/api/news/latest']);

    let result = yield call(http.get, '/api/news/latest');

    console.log(result); //从服务器获取的数据



    yield put({

        type: TYPES.DEMO

    });

};

export default function* saga() {

    yield takeLatest("DEMO-SAGA", working);

};

saga.js -> yield fork

const query1 = function* query1() {

    console.log(1);

    yield delay(2000);

};

const query2 = function* query2() {

    console.log(2);

    yield delay(2000);

};

const working = function* working() {

    /* // 串行

    yield call(query1);

    yield call(query2); */



    /* // 并行:无阻塞调用

    yield fork(query1);

    yield fork(query2); */



    console.log(3);

};

export default function* saga() {

    yield takeLatest("DEMO-SAGA", working);

};

3. 基于redux-saga重写Vote案例

组件

import { useSelector, useDispatch } from 'react-redux';

import * as TYPES from '../store/action-types';

...

const Vote = function Vote() {

    const { supNum, oppNum } = useSelector(state => state.vote),

        dispatch = useDispatch();

    return <VoteBox>

        ...

        <div className="footer">

            <Button type="primary"

                onClick={() => {

                    dispatch({

                        type: TYPES.VOTE_SUP

                    });

                }}>

                支持

            </Button>

            <Button type="primary"

                onClick={() => {

                    dispatch({

                        type: "VOTE-SUP-SAGA"

                    });

                }}>

                异步支持

            </Button>



            <Button type="primary" danger

                onClick={() => {

                    dispatch({

                        type: TYPES.VOTE_OPP

                    });

                }}>

                反对

            </Button>

            <Button type="primary" danger

                onClick={() => {

                    dispatch({

                        type: "VOTE-OPP-SAGA"

                    });

                }}>

                反对异步

            </Button>

        </div>

    </VoteBox>;

};

export default Vote;

saga.js

import { takeLatest, put, delay } from 'redux-saga/effects';

import * as TYPES from './action-types';



const voteSupWorking = function* voteSupWorking() {

    yield delay(2000);

    yield put({

        type: TYPES.VOTE_SUP

    });

};



const voteOppWorking = function* voteOppWorking() {

    yield delay(2000);

    yield put({

        type: TYPES.VOTE_OPP

    });

};



export default function* saga() {

    yield takeLatest("VOTE-SUP-SAGA", voteSupWorking);

    yield takeLatest("VOTE-OPP-SAGA", voteOppWorking);

};