Redux-Saga 学习

简述

  • Reducers 负责处理 action 的 state 更新;
  • Sagas 负责协调那些复杂或异步的操作。

安装

npm install --save redux-saga
1
2
3
4
5
6
7
8
9
10
11
12
// ...
import { createStore, applyMiddleware } from "redux";
import createSagaMiddleware from "redux-saga";

// ...
import { rootSaga } from "./sagas";

const sagaMiddleware = createSagaMiddleware();
const store = createStore(reducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(rootSaga);

const action = (type) => store.dispatch({ type });

辅助函数

用来在一些特定的action被发起到Store时派生任务。
  1. takeEvery
  2. put: 用于创建 dispatch Effect
  3. take: 通过全面控制 action 观察进程来构建复杂的控制流
  4. fork: 无阻塞调用

1
2
yield fetch(url) => yield call(fetch, url)

take

等待dispatch匹配某个action
1
2
3
4
while(true) {
yield take('Click_Action')
yield fork(clickButtonSaga)
}

put

触发某个action,作用和dispatch相同
1
yield put({type: 'CLICK'})
1
2
3
4
5
6
7
8
9
10
11
//具体例子
import { call, put } from "redux-saga/effects";

export function* fetchData(action) {
try {
const data = yield call(fetch, url);
yield put({ type: "FETCH_SUCCESS", data });
} catch (error) {
yield put({ type: "FETCH_FAILED", error });
}
}

call

有阻塞的调用saga或者返回promise的函数,只在触发某个动作

takeEvery

循环监听某个触发动作,通常会使用while循环替代
1
2
3
4
5
import { takeEvery } from "redux-saga/effects";

function* watchFetchData() {
yield takeEvery("FETCH_REQUESTED", fetchData);
}

takeLatest

对于触发多个action的时候,只执行最后一个,其他的会自动取消
1
2
3
4
5
import { takeLatest } from "redux-saga/effects";

function* watchFetchData() {
yield takeLatest("FETCH_REQUESTED", fetchData);
}

fork 和 cancel

通常fork和cancel配合使用,实现非阻塞任务,take是阻塞状态,也就是实现执行take的时候,无法继续向下执行,fork是非阻塞的,同样可以使用cancel取消一个fork任务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function* authorize(user, password) {
try {
const token = yield call(Api.authorize, user, password);
yield put({ type: "LOGIN_SUCCESS", token });
} catch (error) {
yield put({ type: "LOGIN_ERROR", error });
}
}

function* loginFlow() {
while (true) {
const { user, password } = yield take("LOGIN_REQUEST");
yield fork(authorize, user, password);
yield take(["LOGOUT", "LOGIN_ERROR"]);
yield call(Api.clearItem("token"));
}
}
当执行yield fork(authorize, user, password),同时执行yield take(['LOGOUT', 'LOGIN_ERROR'])

错误处理

我们假设远程数据读取因为某些原因失败了,API函数API.fetch返回一个被拒绝(rejected)的Promise
1
2
3
4
5
6
7
8
9
10
11
12
import Api from './Api'
import { call, put } from 'redux-saga/effects'

// ...
function* fetchProducts() {
try {
const products = yield call(Api.fetch, '/products')
yield put({ type: 'PRODUCTS_RECEIVED', products })
} catch (err) {
yield put({ type: 'PRODUCTS_REQUEST_FAILED', err)
}
}

takeEvery 的使用

saga中的take并不支持action的循环调用,即遍历数组执行action,为解决该问题,可以使用takeEvery来执行action,此时即可实现遍历数组执行action。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { call, put, takeEvery } from "redux-saga/effects";

function* fetchUsr(action) {
const payload = action;
try {
const user = yield call(api.getUser, payload);
yield put({ type: GET_ENTITIES_USER, user });
} catch (err) {
console.log("err: %o", err);
}
}

function* mySaga() {
//在每个 'GET_USER' action 被发起时调用 fetchUser
//允许并发(译注:即同时处理多个相同的 action)
takeEvery(actions.GET_USER, fecthUser);
}