美文网首页前端大宝剑React.js
09-采用React Saga的心路历程

09-采用React Saga的心路历程

作者: 七玄之主 | 来源:发表于2019-07-05 20:34 被阅读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 可以完成我们正常需求,但是它存在一些问题。

    • Action 层会随着项目需求增长而不断扩大。
    • 异步 Action 不能很好的进行单元测试。
    • 不能够更好的组织业务的流程控制,每个异步 Action 都是相对独立的,无关联的。
    • Action 包含了副作用,不能保持为一个 Plain Object。
    • ......(以后发现继续追加)

    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

    • put(Action) 创建 dispatch 的 Effect,告诉 middleware 发起 Action 操作。
    • call(method | generator, arg1, arg2, ...) 告诉 middleware 使用给定的参数 arg1, arg2, ... 调用给定的 method 或 generator 函数,另外一种写法可以允许我们调用指定对象的方法 yield call([obj, obj.method], arg1, arg2, ...)。适合调用返回Promise 结果的函数。
    • apply(obj, obj.method, [arg1, arg2, ...]) 与 call 指令功能相同,就写法不一样。
    • select() 返回当前完整的 State 树,与 getState 类似,也可以指定对应的 selector 作为参数,来返回指定部分的 State。
    • take(Action | '*') 告诉 middleware 等待一个指定或者满足匹配符 * 的 Action。使用 take 就可以组织我们复杂的流程控制。
    • fork(method | generator, arg1, arg2, ...) 相对于take,表示一个无阻塞调用,告诉 middleware 使用给定的参数 arg1, arg2, ... 调用给定的 method 或 generator 函数。
    • cancel(task) 命令 middleware 取消之前的一个 fork 任务。与之对应的cancelled指令可以指定取消任务需要执行的操作。
    • race(effects) 类似Promise.race功能,在多个 Effects 之间触发一个竞赛,谁先完成就结束整个 Effect,失败方自动取消。

    saga 相关 Wraaper Effect

    • takeEvery(Action | '*', function* do(){}) 检测到指定或者满足匹配符 * 的 Action 发起到 store 以后,触发后续 do 操作指令,允许多个 do 操作同时发生。这个和 redux-thunk 功能相似。
    • takeLatest(Action | '*', function* do(){}) 只相应最新的 do 操作。

    以上是比较常用的,还想了解更多请查阅 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 层我们是不用动的。如此启动看看效果吧。

    相关文章

      网友评论

        本文标题:09-采用React Saga的心路历程

        本文链接:https://www.haomeiwen.com/subject/bwlrhctx.html