React Native Redux Typescript使用 redux-thunk or redux-saga

上一篇博客我们用理论和代码实践介绍了 使用redux 发起action 在reducers里生成state 然后重新渲染组件

Redux 的核心理念是严格的单向数据流,只能通过 dispatch(action) 的方式修改 store,流程如下:

view ->  action -> reducer -> store

但是在业务复杂的以及和api数据对接的过程中肯定会遇到大量的异步操作。我们如何来解决这些场景呢?

redux中间件

什么是redux中间件

redux中间件
这里我们先从redux的中间件说起, 中间件,顾名思义:进行中间处理的物件。类似于面向对象编程的AOP编程思想(不了解AOP的可以忽略这句话)
简单的说:中间件可以控制在store dispatch action之前和之后的业务逻辑。也就是说 中间件实现了改写 store.dispatch 方法实现了action -> reducer的拦截的行为。
如果我们分别注册三个中间件: 中间件A 中间件B 中间件C
那么

中间件A -> 中间件B-> 中间件C-> 原始 dispatch -> 中间件C -> 中间件B -> 中间件A

和异步处理的关系

综上所述:中间件可以领过的改变 dispatch的时机,这样我们就可以很方便的处理异步场景了。
因此各种 redux异步处理中间件应运而生。比较知名的有redux-thunkredux-saga

redux-thunk

redux-thunk中间件可以让action创建函数先不返回一个action对象,而是返回一个函数,函数传递两个参数(dispatch,getState),在函数体内进行业务逻辑的封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function add() {
return {
type: 'ADD',
}
}

function addIfOdd() {
return (dispatch, getState) => {
const currentValue = getState();
if (currentValue % 2 == 0) {
return false;
}
//分发一个任务
dispatch(add())
}
}

详细代码可以查看分支:https://github.com/YahuiWong/react-native-typescript/tree/redux-thunk

redux-saga

sages 采用 Generator 函数来 yield Effects(包含指令的文本对象)。Generator 函数的作用是可以暂停执行,再次执行的时候从上次暂停的地方继续执行。Effect 是一个简单的对象,该对象包含了一些给 middleware 解释执行的信息。你可以通过使用 effects API 如 fork,call,take,put,cancel 等来创建 Effect。( redux-saga API 参考)

如 yield call(fetch, ‘/products’) 即 yield 了下面的对象,call 创建了一条描述结果的信息,然后,redux-saga middleware 将确保执行这些指令并将指令的结果返回给 Generator:

1
2
3
4
5
6
// Effect -> 调用 fetch 函数并传递 `./products` 作为参数
{
type: CALL,
function: fetch,
args: ['./products']
}

与 redux-thunk 不同的是,在 redux-saga 中,UI 组件自身从来不会触发任务,它们总是会 dispatch 一个 action 来通知在 UI 中哪些地方发生了改变,而不需要对 action 进行修改。redux-saga 将异步任务进行了集中处理,且方便测试。

dispacth({ type: ‘FETCH_REQUEST’, url: //} );
所有的东西都必须被封装在 sagas 中。sagas 包含3个部分,用于联合执行任务:

worker saga
做所有的工作,如调用 API,进行异步请求,并且获得返回结果
watcher saga
监听被 dispatch 的 actions,当接收到 action 或者知道其被触发时,调用 worker saga 执行任务
root saga
立即启动 sagas 的唯一入口

☀ 如何使用?
首先,我们得在文件入口中加入 saga 中间件,并且启动它,它会一直运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//...
import { createStore, applyMiddleware} from 'redux';
import createSagaMiddleware from 'redux-saga';
import appReducer from './reducers';
import rootSaga from './saga';
//...

const sagaMiddleware = createSagaMiddleware()

const store=createStore(rootReducer,applyMiddleware(sagaMiddleware));

sagaMiddleware.run(rootSaga)

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

然后,就可以在 sagas 文件夹中集中写 saga 文件了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import {delay} from 'redux-saga';
import {put,takeEvery,all} from 'redux-saga/effects';
import {ADD} from './actionsTypes';
function* addSync(){
yield delay(1000);
yield put({type:ADD})
}
function* watchaddSync(){
yield takeEvery("addSync",addSync)
}

export default function* rootSaga(){
yield all([
watchaddSync()
])
}

在 redux-saga 中的基本概念就是:sagas 自身不真正执行副作用(如函数 call),但是会构造一个需要执行作用的描述。中间件会执行该副作用并把结果返回给 generator 函数。

对上述例子的说明:

(1)引入的 redux-saga/effects 都是纯函数,每个函数构造一个特殊的对象,其中包含着中间件需要执行的指令,如:call(fetchUrl, url) 返回一个类似于 {type: CALL, function: fetchUrl, args: [url]} 的对象。

(2)在 watcher saga watchFetchRequests中:

首先 yield take(‘FETCH_REQUEST’) 来告诉中间件我们正在等待一个类型为 FETCH_REQUEST 的 action,然后中间件会暂停执行 wacthFetchRequests generator 函数,直到 FETCH_REQUEST action 被 dispatch。一旦我们获得了匹配的 action,中间件就会恢复执行 generator 函数。

下一条指令 fork(fetchUrl, action.url) 告诉中间件去无阻塞调用一个新的 fetchUrl 任务,action.url 作为 fetchUrl 函数的参数传递。中间件会触发 fetchUrl generator 并且不会阻塞 watchFetchRequests。当fetchUrl 开始执行的时候,watchFetchRequests 会继续监听其它的 watchFetchRequests actions。当然,JavaScript 是单线程的,redux-saga 让事情看起来是同时进行的。

(3)在 worker saga fetchUrl 中,call(fetch,url) 指示中间件去调用 fetch 函数,同时,会阻塞fetchUrl 的执行,中间件会停止 generator 函数,直到 fetch 返回的 Promise 被 resolved(或 rejected),然后才恢复执行 generator 函数。

最后,总结一下 redux-saga 的优点:

(1)声明式 Effects:所有的操作以JavaScript对象的方式被 yield,并被 middleware 执行。使得在 saga 内部测试变得更加容易,可以通过简单地遍历 Generator 并在 yield 后的成功值上面做一个 deepEqual 测试。
(2)高级的异步控制流以及并发管理:可以使用简单的同步方式描述异步流,并通过 fork 实现并发任务。
(3)架构上的优势:将所有的异步流程控制都移入到了 sagas,UI 组件不用执行业务逻辑,只需 dispatch action 就行,增强组件复用性。

详细代码可以查看分支:https://github.com/YahuiWong/react-native-typescript/tree/redux-saga

参考:
https://segmentfault.com/a/1190000007248878#articleHeader7