Skip to content
On this page

redux-saga

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

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

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

redux-thunk与redux-saga的比较

redux中的数据流

action ———> reducer ———> state

action是一个纯粹对象(plain object)

reducer是一个纯函数(和外界没有任何关系)

都只能处理同步的操作

redux-thunk中间件的处理流程

action1 ———> middleware ———> action2 ———> reducer ———> state

JavaScript
/* 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

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

redux-saga中间件的工作流程

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

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

redux-saga的基础知识

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

安装中间件

bash
npm install redux-saga
yarn add redux-saga
1
2

使用中间件

store/index.js

JavaScript
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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14

store/action-types.js

JavaScript
export const VOTE_SUP = "VOTE_SUP";
export const VOTE_OPP = "VOTE_OPP";

export const DEMO = "DEMO";
1
2
3
4

store/reducer

JavaScript
// 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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

Demo.jsx组件中

JavaScript
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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

saga.js 工作流程

第一部分:创建监听器「基于saga的辅助函数」

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 中的监听器执行

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

jsx
<Button type="primary"
    onClick={() => {
        dispatch({
            type: "DEMO-SAGA",
            payload: 10
        });
    }}>
    按钮
</Button>
1
2
3
4
5
6
7
8
9

saga.js -> take 函数的运用

JavaScript
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);
    }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

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

JavaScript
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); */
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

saga.js -> yield call/select…

JavaScript
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);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

saga.js -> yield fork

JavaScript
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);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

基于redux-saga重写Vote案例

组件

jsx
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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

saga.js

JavaScript
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);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
沪ICP备20006251号-1