Redux Saga

2020年05月31号 00:00 — Written by Z1206
#React#Redux#Frontend#WebDev

Redux-Saga是什么?

Redux-saga是一个Redux Middleware

Redux-saga是用来做什么的?

是用来更优雅地管理side effects的。

那什么是side effects?

Side effects are the most common way that a program interacts with the outside world (people, filesystems, other computers on networks) [from Wikipedia]

side effect是程序和外界(人,文件系统,网上的其他电脑)交互最常见的一个方式。

对于前端来讲,side effect一般指异步网络请求。

那effect又是什么

An effect is a plain JavaScript Object containing some instructions to be executed by the saga middleware.

一个effect是个普通的JS对象,包含一些会被中间件saga执行的指令。

在redux-saga的世界里,所有的effect都必须被yield才会执行。而且原则上来讲,所有的yield后面也只能跟随着effect,以保证代码的易测性。比如:

yield fetch(UrlMap.fetchData); // 应该用call: yield call(fetch, UrlMap.fetchData);

task是什么

task是generator方法的执行环境,所有的saga的generator方法都跑在task里面。

redux-saga的优点都有什么

用于管理前端异步网络请求,本质上就是为了解决异步action的问题。

  • side effects转移到单独的saga.js里面,不再掺杂于action.js里面,保持action的简单纯粹,又使得异步操作可以被集中处理。
  • saga的机制在处理复杂的问题上更顺手,有更加细腻的控制流(所有的saga都可以被中断)。
  • 对比thunk,dispatch的参数依然是一个纯粹的action
  • 每一个saga都是个generator function,代码可以采用同步书写的方式处理异步逻辑,解决了backcall hell问题,代码易读易懂。
  • 受益于generator function的saga实现,代码异常or请求失败都可以通过try/catch来捕获处理。

如何使用redux-saga

举例简单的一个hello saga

// 单独的文件:sagas.js, 统一管理副作用: export function* helloSaga() { console.log('Hello Sagas!'); } // 将saga和store关联起来, 入口文件 main.js import { createStore, applyMiddleware } from 'redux'; import createSagaMiddleware from 'redux-saga'; import helloSaga from './sagas' import rootReducer from './reducers' // 创建 saga middleware const sagaMiddleware = createSagaMiddleware(); // 创建 store const store = createStore( rootReducer, applyMiddleware(sagaMiddleware) // 注入 saga middleware ); // 启动 saga sagaMiddleware.run(helloSaga); // 省略ReactDOM.render部分的代码...

这样就能看到Hello Sagas!了。

代码分析:

Line 8 - 9: 通过redux-saga提供的工厂函数createSagaMiddleware创建sagaMiddleware(当然创建时候也可以传递一些可选的配置参数)
Line 11 - 15: 创建store instance,注入saga中间件。意味着,以后每次运行store.dispatch(action)的时候,数据流都会经过sagaMiddleware这一道工序,进行必要的"加工处理"(比如发送一个异步请求)。
Line 17 - 18: 启动saga,调用run方法使得generator可以开始执行,也就是执行rootSaga。通常是程序的一些初始化操作(比如:初始化数据,注册action监听)。

再看另一个例子,有三个按钮,第一个按一下1秒后计数器会显示+1,第二个按钮按一下+1,第三个按钮按一下-1。

reducer里面有加一的处理:

... case 'INCREMENT': return { ...state, count: state.count + 1 } ...

sagas.js

import { all, put, takeEvery } from 'redux-saga/effects'; const delay = (ms) => new Promise(res => setTimeout(res, ms)); // worker Saga: 执行异步的 increment 任务 export function * incrementAsync () { yield delay(1000); // middleware 拿到一个 yield 后的 Promise,暂停1s后再继续执行 yield put({ type: 'INCREMENT' }); // 告诉 middleware 发起一个 INCREMENT 的 action。 } // watcher Saga: 在每个INCREMENT_ASYNC上生成一个新的incrementAsync任务 export function * watchIncrementAsync () { yield takeEvery('INCREMENT_ASYNC', incrementAsync); } // 启动saga们 export default function * rootSaga () { yield all([ watchIncrementAsync(), helloSaga() ]); }

和前面的例子相似,只是把helloSaga换成rootSaga即可。

代码分析:

Sagas 是被实现为 Generator functions 的
Line 2: 创建一个delay函数,返回一个Promise,它在指定的毫秒数后解析。
Line 5 - 8: incrementAsync 这个 Saga 会暂停,直到 delay 返回的 Promise 被 resolve,即 1000ms 之后;
Line 6: middleware 拿到一个 yield 后的 Promise,middleware 暂停 Saga,直到 Promise 完成。一旦 Promise 被 \resolve,middleware 会恢复 Saga 接着执行,直到遇到下一个 yield。
Line 7: 这里就是第二个yield啦,这里的 put({type: 'INCREMENT'}) 就是一个Effect,Effect 是纯js对象,其中包含了给 middleware 执行的指令;当 middleware 拿到被Saga yield的Effect的时候,也会暂停Saga,直到Effect 执行完成,然后Saga 会再次被恢复。
Line 11 - 13: 写一个watcher saga,用redux-saga的api takeEvery 来监听所有的 INCREMENT_ASYNC action,并在 action 被匹配时执行 incrementAsync 任务。
Line 15 - 18: 有了Saga,,现添加一个rootSaga来负责启动所有Saga,用了all api,如果有其他Saga都能一起启动。

Created by Z1206 © 2020