前端大宝剑React.js

09-采用React Saga的心路历程

2019-07-05  本文已影响7人  七玄之主

我们在之前的实现中,对于异步 Action 的调用使用了 redux-saga 中间件。thunk 中间件通过增强了返回可调用函数的功能,也就允许了我们可以实现如下所示的异步 Action 。

actions/novel.ts

// 普通 Action
const fetchNovels = createAction(ACTION_TYPES.FETCH_NOVELS);
const fetchNovelsOK = createAction(ACTION_TYPES.FETCH_NOVELS_OK);
const fetchNovelsNG = createAction(ACTION_TYPES.FETCH_NOVELS_NG);

// 异步 Action
export const searchNovels = () => (dispatch: Dispatch) => {
  dispatch(fetchNovels()); // {type: 'FETCH_NOVELS'}
  queryNovels().then(resp => {
    if (resp.isAxiosError) {
      dispatch(fetchNovelsNG(resp)); // {type: 'FETCH_NOVELS_NG', payload: error, error: true}
    } else {
      dispatch(fetchNovelsOK(resp.novel)); // {type: 'FETCH_NOVELS_OK', payload: json}
    }
  });
};

使用 thunk 可以完成我们正常需求,但是它存在一些问题。

redux-saga 也是一个类似 redux-thunk 的增强 store 功能的中间件,它是可测试的,并提供了声明式指令。saga 使用了 ES6 的 Generator 功能,让异步的流程更易于读取,写入和测试,并且将流程控制从Action Creator 中抽出,简化了 Action 层,保持了 Action 层的纯净。

我们将原来定义在 Action 层里的副作用代码,转移到 saga 层来实现。function* 就是ES6 的 Generator 函数实现方式。saga 内部的业务流程控制都是通过一个一个的 yield 来完成的,你可以直接 yield 一个 promise(当然由于不利于单元测试,不推荐这样写),也可以直接使用 saga 所提供的一些申明式的命令,也就是 Effect。每一个 yield 的 Effect 都会传递到 redux-saga 中间件被解释执行,如果指令是 promise,saga 就会暂停等到 promise 返回。接着就执行下一个 yield 指令,你也可以通过 if,for 等控制语句来构建更复杂的流程。

saga 相关 基础 Effect

saga 相关 Wraaper Effect

以上是比较常用的,还想了解更多请查阅 API 参考
。并且所有的 Effect 都是生成简单对象后,发送给 saga middleware,由 middleware 来根据effect 的类型来完成具体的调用。所以才保证了 saga 来实现相关的副作用是可测试的。

现在开始实际编码吧。首先执行命令安装yarn add redux-saga

在根目录新建文件夹 sagas,新定义 saga 层来定义我们的业务流程,新增 novel.ts

import { call, put, take } from "redux-saga/effects";
import { queryNovels } from "../services/novelapi";
import { fetchNovels, fetchNovelsOK, fetchNovelsNG } from "../actions/novel";

export function* watchSearchNovels() {
  // 无限循环保证 saga 一直在后台运行监视
  while (true) {
    // 阻塞直到 fetchNovels Action发起
    yield take(fetchNovels);
    try {
      // 异步调用
      const data = yield call(queryNovels);
      // 通知store发起fetchNovelsOK操作
      yield put(fetchNovelsOK(data.novel));
    } catch (error) {
      // 通知store发起fetchNovelsNG操作
      yield put(fetchNovelsNG(error));
    }
  }
}

删除原来 actions/novel.ts 中定义的异步 Action 代码

import { ACTION_TYPES } from "../constants";
import { createAction } from "redux-actions";

// 普通 Action
export const fetchNovels = createAction(ACTION_TYPES.FETCH_NOVELS); // {type: 'FETCH_NOVELS'}
export const fetchNovelsOK = createAction(ACTION_TYPES.FETCH_NOVELS_OK); // {type: 'FETCH_NOVELS_OK', payload: json}
export const fetchNovelsNG = createAction(ACTION_TYPES.FETCH_NOVELS_NG); // {type: 'FETCH_NOVELS_NG', payload: error, error: true}

configure-store.ts 里移除 thunk 中间件的代码,替换为 saga 。

// saga 中间件
const sagaMiddleware = createSagaMiddleware();
// 创建store
export const store = createStore(
  // 跟reducer
  rootReducer,
  // 应用中间件
  applyMiddleware(
    sagaMiddleware,
    routerMiddleware(history),
    loggerMiddleware,
    reduxCatch((error: Error) => {
      console.error("Redux Action 调用出错了");
      console.error(error);
    })
  )
);
// 启动 saga
sagaMiddleware.run(watchSearchNovels);

reducer 层我们是不用动的。如此启动看看效果吧。

上一篇 下一篇

猜你喜欢

热点阅读