跳到主要内容

redux-saga

redux-saga官网

redux-saga 是一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的 library,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易。

可以想像为,一个 saga 就像是应用程序中一个单独的线程,它独自负责处理副作用。 redux-saga 是一个 redux 中间件,意味着这个线程可以通过正常的 redux action 从主应用程序启动,暂停和取消,它能访问完整的 redux state,也可以 dispatch redux action

redux-saga 使用了 ES6Generator 功能,让异步的流程更易于读取,写入和测试。(如果你还不熟悉的话,这里有一些介绍性的链接) 通过这样的方式,这些异步的流程看起来就像是标准同步的 Javascript 代码。(有点像 async/await,但 Generator 还有一些更棒而且我们也需要的功能)。

你可能已经用了 redux-thunk 来处理数据的读取。不同于 redux thunk,你不会再遇到回调地狱了,你可以很容易地测试异步流程并保持你的 action 是干净的

API


redux-saga API

  • createSagaMiddleware(options)

    • 作用: 创建一个 Redux middleware,并将 Sagas 连接到 Redux Store
  • middleware.run(saga, ...args)

    • 作用: 动态地运行 saga。只能 用于在 applyMiddleware 阶段 之后 执行 Saga

redux-saga/effects API

  • put(action)

    • 作用: 创建一个 Effect 描述信息,用来命令 middlewareStore 发起一个 action。 这个 effect 是非阻塞型的,并且所有向下游抛出的错误(例如在 reducer 中),都不会冒泡回到 saga 当中。

    • 语法: 相当于diapatch

      yield put({type:'',payload:''});
  • call(fn, ...args)

    • 作用: 创建一个 Effect 描述信息,用来命令 middleware 以参数 args 调用函数 fn

    • 语法: 异步调用函数

      const result = yield call(异步函数,action);
  • fork(fn, ...args)

    • 作用: 创建一个 Effect 描述信息,用来命令 middleware 以 非阻塞调用 的形式执行 fn
  • take(pattern)

    • 作用: 创建一个 Effect 描述信息,用来命令 middlewareStore 上等待指定的 action。 在发起与 pattern 匹配的 action 之前,Generator 将暂停。
  • apply(context, fn, [args])

    • 作用: call([context, fn], ...args) 的另一种写法。
  • takeEvery(pattern, saga, ...args)

    • 作用: 在发起(dispatch)到 Store 并且匹配 pattern 的每一个 action 上派生一个 saga

    • 实现:

      const takeEvery = (patternOrChannel, saga, ...args) => fork(function*() {
      while (true) {
      const action = yield take(patternOrChannel)
      yield fork(saga, ...args.concat(action))
      }
      })

使用


架构设计方案A


store目录结构:

|- index.js 
|- reducers.js
|- watchers.js
  1. 开始安装相关依赖
yarn add redux react-redux redux-logger redux-saga -S
  1. store > index.js 管理store对象创建、配置
import Logger from 'redux-logger'
import watchers from './watchers';
import { countReducer } from "./reducers";

import createSagaMiddleware from 'redux-saga';
import { createStore, combineReducers, applyMiddleware } from "redux";

const reducer = combineReducers({
count: countReducer,
});
const saga = createSagaMiddleware();
const store = createStore(reducer, applyMiddleware(saga, Logger))
saga.run(watchers);

export default store;
  1. store > reducers.js 管理reducer
export const countReducer = (state = 0, action) => {
const { type, payload } = action;
switch (type) {
case "add":
return state + payload;
case "min":
return state - payload;
default:
return state;
}
}
  1. store > watchers.js 管理saga
import { call, put, takeEvery } from 'redux-saga/effects';

const asyncHandle = {
add(argu) {
console.log('add函数参数',argu);
return new Promise((resolve) => {
resolve(1);
});
},
min(argu) {
console.log('min函数参数',argu);
return new Promise((resolve) => {
resolve(1);
});
}
}

const effects = {
*handleAddCount(action) {
try{
const result = yield call(asyncHandle.add, action);
yield put({ type: 'add', payload: result });
} catch(error){
console.log(error);
}
},
*handleMinCount(action) {
try{
const result = yield call(asyncHandle.min, action);
yield put({ type: 'min', payload: result });
}catch(error){
console.log(error);
}
}
}

function* watcherCount() {
yield takeEvery('sagaAddCount', effects.handleAddCount);
yield takeEvery('sagaMinCount', effects.handleAddCount);
}

export default watcherCount;
  1. 根目录 > src > index.js 用于传递store对象
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import store from './store'
import { Provider } from 'react-redux';

const render = () => {
ReactDOM.render(
<Provider store={store}><App /></Provider>,
document.getElementById('root')
);
}

render();
  1. 根目录 > src > App.js 用于调用saga
import React from 'react';
import { connect } from 'react-redux';

function App(props) {
return <div>
{props.count}
<button onClick={() => props.add(1)}>增加</button>
</div>
}

const mapStateToProps = (state) => {
return {
count: state.count
}
}
const mapDispatchToProps = {
add: (value) => ({ type: 'sagaAddCount', payload: value }),
min: (value) => ({ type: 'sagaMinCount', payload: value })
}
const ConnectApp = connect(mapStateToProps, mapDispatchToProps)(App);

export default ConnectApp;