之前两篇已经介绍了redux知识点和redux的工具包@reduxjs/toolkit;现在我们来一起学习redux-saga。
其他几篇:
react数据集中式管理第二篇--@reduxjs/toolkit
react数据集中式管理第四篇--@rematch/core
什么是redux-saga
redux-saga是一个用于管理redux应用异步操作的中间件,redux-saga通过创建sagas将所有异步操作逻辑收集在一个地方集中处理,可以用来代替redux-thunk中间件。
关于saga:
- sagas是通过generator函数来创建的
- sagas监听发起的action,然后决定来执行什么
- redux-saga中所有的任务都是通过yield effects来完成
- 因为使用了generator函数,故可以使用同步的方式来写异步代码
- 启动的任务可通过手动来取消
redux-saga使用教程以及api
安装: yarn add redux-saga
配置:将saga引入后挂载到store:
引入saga;使用createSagaMiddleware注册,将注册好的变量挂载到中间件去加载,然后启动run
下面一边学习api,一边学习在项目中的使用方式:
先把创建的action展示一下,虽然上一篇有写:
// store/modules/action.js
import { createAction } from "@reduxjs/toolkit";
export const getAgentUserAction = createAction("posts/update");
// 返回给定 action type 字符串的 action creator 函数,该函数本身已定义了 toString(),因此可以代替常量类型使用
// {type: "posts/update", payload: {}}
console.log("getAgentUserAction", getAgentUserAction({}));
export const increment = createAction("counter/increment");
export const decrement = createAction("counter/decrement");
export const incrementByAmount = createAction("counter/incrementByAmount");
export const sagaAddUser = createAction("saga/addUser");
export const sagaTake = createAction("saga/take");
export const sagaUpdateUser = createAction("saga/updateUser");
takeEvery
作用:挂载到run之后类似于监听type,若有对应的type触发则执行对应fn
格式:yield takeEvery(action, fn) 此fn:function* fn(action) {}
// 如果有对应type的action触发,就执行fn()函数
第一个参数action可以是字符串'xxx',或者是createAction生成的toString()
代码解析如下: dispatch(sagaAddUser("张三")); // 这里执行之后就会saga.js里面的addUser就会被调用了
// saga.js
import { sagaAddUser } from "./modules/action";
import { call, put, takeEvery } from "redux-saga/effects"; // 引入相关函数
function* addUser(action) {
console.log(action, "addUser"); // {type: "saga/addUser", payload: "张三"}
}
function* rootSaga() {
// 在store.js中,执行了 sagaMiddleware.run(rootSaga)
yield takeEvery(sagaAddUser, addUser); // 如果有对应type的action触发,就执行addUser()函数
//yield takeEvery("saga/addUser", addUser); // 这种写法与yield takeEvery(sagaAddUser, addUser) 是一样的
}
export default rootSaga; // 导出rootSaga,被store.js文件import
// user.js
// 这里执行之后就会saga.js里面的addUser就会被调用了
dispatch(sagaAddUser("张三"));
take
作用:暂停generator,匹配的action被发起时,恢复执行
yield take(action)
// 当addUser被调用时,会打印addUser;然后执行到 yield take(sagaTake)时就暂停了,不会往下执行,故不会打印 take ok
// 当经过2秒之后,执行了 dispatch(sagaTake()) 唤醒 take;则2秒之后generator往下执行打印出 take ok
// saga.js
import { sagaAddUser, sagaTake } from "./modules/action";
import { call, put, takeEvery, fork, take } from "redux-saga/effects"; // 引入相关函数
function* addUser(action) {
console.log(action, "addUser"); // {type: "saga/addUser", payload: "张三"}
yield take(sagaTake);
console.log("take ok");
}
// user.js
import { sagaAddUser, sagaTake } from "../store/modules/action";
const add = () => {
dispatch(sagaAddUser("张三"));
setTimeout(() => {
console.log("延迟2秒");
dispatch(sagaTake());
}, 2000);
};
fork(fn, ...args)
无阻塞的执行fn,执行fn时,不会暂停generator;相当于异步操作;
yield fork(fn, ...args)的结果是一个Task对象;...args作为fn的参数
// 以下是先打印res,然后过了3秒之后打印 延时3秒;故可以将fork看成异步操作
// saga.js
function getInfo(value) {
return new Promise((resolve) => {
setTimeout(() => {
console.log("延时3秒");
resolve(value);
}, 3000);
});
}
function* addUser(action) {
console.log(action, "addUser"); // {type: "saga/addUser", payload: "张三"}
// 以下是先打印res,然后过了3秒之后打印 延时3秒;故可以将fork看成异步操作
const res = yield fork(getInfo, "ok"); // 无阻塞执行,即getInfo触发后,就会执行下面的console语句
console.log(res, "res"); // 打印出来的是一个task对象
}
cancel(task)
取消任务(task),task是通过fork函数来创
参考链接:redux-saga-in-chinese.js.org/docs/advanc…
function* addUser(action) {
console.log(action, "addUser"); // {type: "saga/addUser", payload: "张三"}
const task = yield fork(getInfo, "ok");
yield cancel(task) // 取消 fork 这个任务
}
call(fn, ...args)
阻塞的执行fn,call()执行完,才会往下执行; 跟fork差不多,只不过fork是异步的;
相当于同步操作;后面的代码只能等call()执行完
// 以下是过了3秒之后打印 延时3秒,然后再打印 ok; 故可以将call看成同步操作,相当于 async await
// saga.js
function getInfo(value) {
return new Promise((resolve) => {
setTimeout(() => {
console.log("延时3秒");
resolve(value);
}, 3000);
});
}
function* addUser(action) {
console.log(action, "addUser"); // {type: "saga/addUser", payload: "张三"}
// 以下是过了3秒之后打印 延时3秒,然后再打印 ok; 故可以将call看成同步操作,相当于 async await
const data = yield call(getInfo, "ok"); // 阻塞执行,只有这里返回结果才会往下执行
console.log(data, "data"); // 打印 ok
}
put(action)
yeild put(action)
发起一个action到store
yield put(action) 可以理解为 dispatch(action);可以直接触发store里面的动作函数,也可以继续触发saga;
// yield put()直接触发store里面的动作函数
import { sagaAddUser, sagaTake, increment } from "./modules/action";
function* addUser(action) {
console.log(action, "addUser"); // {type: "saga/addUser", payload: "张三"}
const data = yield call(getInfo, "ok");
console.log(data, "data");
// 等待3秒后打印 ok,然后发起一个action: increment到store
// 其实就是可以看成 dispatch(increment(1))
yield put(increment(1));
}
////////////////////////////////////////
// 继续触发 saga
import { sagaAddUser, sagaTake, increment, sagaUpdateUser } from "./modules/action";
function* updateUser(action) {
console.log("updateUser", action); // {type: "saga/updateUser", payload: {}}
yield put(increment()); // 触发store里面的动作函数
}
function* addUser(action) {
console.log(action, "addUser"); // {type: "saga/addUser", payload: "张三"}
const data = yield call(getInfo, "ok");
console.log(data, "data");
//yield put(increment());
yield put(sagaUpdateUser({})); // 继续触发saga
}
function* rootSaga() {
yield takeEvery(sagaAddUser, addUser);
yield takeEvery(sagaUpdateUser, updateUser);
}
race
不用等待所有任务完成
const {posts, timeout} = yield race({
posts: call(fetchApi, '/posts'),
timeout: call(delay, 1000)
})
select(selector, ...args)
yield select((state) => state)
import { call, put, takeEvery, fork, take, select } from "redux-saga/effects"; // 引入相关函数
const info = yield select((state) => state);
console.log(info) // 打印的是所有模块的state
同时执行多个任务
// 跟race有些区别的
// 正确写法, effects 将会同步执行
const [users, repos] = yield [
call(fetch, '/users'),
call(fetch, '/repos')
]
debounce
import { debounce } from "redux-saga/effects"; // 引入相关函数
import { sagaDebounce } from "./modules/action";
function* debounceFn(action) {
console.log(action, "debounce");
}
function* rootSaga() {
// 多次调用,只会在最后一次调用的时候经过3秒后执行debounceFn函数
yield debounce(3000, sagaDebounce, debounceFn);
}
////////
// 那么这里使用lodash的debounce会有一些问题
function* debounceFn(action) {
console.log(action, "debounce");
}
const _debounce = _.debounce(debounceFn, 3000);
function* rootSaga() {
yield takeEvery(sagaDebounce, _debounce);
//yield debounce(3000, sagaDebounce, debounceFn);
}
使用lodash的debounce,首次调用而且无论调用多少次都不会执行debounceFn函数;就算经过3秒也不会执行;
接着再次调用(这里要等待3秒之后,要不然会被认为是首次调用范围内),然后debounceFn函数就直接执行了,并没有经过3秒(实际上要等待3秒才执行);而且这里接收到的参数是上一次调用的参数;
首次调用:
经过3秒再次调用:
可以看到改变了输入框的值,再次调用,函数就直接执行了,并没有经过3秒,而且打印的值是上一次调用的值而不是现在输入框的值;
所以在saga里面需要用到debounce的话,最好还是用redux-saga里面提供的debounce;至于lodash的debounce有问题的原因我是猜想跟saga的generator有关;我们在yield takeEvery(sagaDebounce, _debounce);的时候_debounce那里返回的并不是一个带*号的即generator函数,故失效了;
暂时就介绍这么多,其他的可以自己上网找找看哦,怎样,是不是学废了!
看到这里基本上同步异步操作都ok了;
但是这样还是避免不了要写去创建action,比如createAction,而且异步操作还需要中间件操作(虽然用起来还挺不错的
那有没有一种方法将异步操作和不需要写action一起解决掉,那这个时候可以借助@rematch/core,欲知后事如何请听下篇文章分解。