react数据集中式管理第三篇--redux-saga

602 阅读7分钟

之前两篇已经介绍了redux知识点和redux的工具包@reduxjs/toolkit;现在我们来一起学习redux-saga。

其他几篇:

react数据集中式管理第一篇--Redux初认识

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

image.png

下面一边学习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"// 引入相关函数
functionaddUser(action) {
  console.log(action, "addUser"); // {type: "saga/addUser", payload: "张三"}
}
functionrootSaga() {
  // 在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"// 引入相关函数
functionaddUser(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);
    });
}
functionaddUser(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…

functionaddUser(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);
  });
}
functionaddUser(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";
functionaddUser(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";
functionupdateUser(action) {
  console.log("updateUser", action); // {type: "saga/updateUser", payload: {}}
  yield put(increment());  // 触发store里面的动作函数
}
functionaddUser(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
}
functionrootSaga() {
  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";
functiondebounceFn(action) {
  console.log(action, "debounce");
}
functionrootSaga() {
  // 多次调用,只会在最后一次调用的时候经过3秒后执行debounceFn函数
  yield debounce(3000, sagaDebounce, debounceFn);
}

////////
// 那么这里使用lodash的debounce会有一些问题
functiondebounceFn(action) {
  console.log(action, "debounce");
}
const _debounce = _.debounce(debounceFn, 3000);
functionrootSaga() {
  yield takeEvery(sagaDebounce, _debounce);
  //yield debounce(3000, sagaDebounce, debounceFn);
}

使用lodash的debounce,首次调用而且无论调用多少次都不会执行debounceFn函数;就算经过3秒也不会执行;

接着再次调用(这里要等待3秒之后,要不然会被认为是首次调用范围内),然后debounceFn函数就直接执行了,并没有经过3秒(实际上要等待3秒才执行);而且这里接收到的参数是上一次调用的参数;

首次调用:

image.png

经过3秒再次调用:

可以看到改变了输入框的值,再次调用,函数就直接执行了,并没有经过3秒,而且打印的值是上一次调用的值而不是现在输入框的值;

image.png

所以在saga里面需要用到debounce的话,最好还是用redux-saga里面提供的debounce;至于lodash的debounce有问题的原因我是猜想跟saga的generator有关;我们在yield takeEvery(sagaDebounce, _debounce);的时候_debounce那里返回的并不是一个带*号的即generator函数,故失效了;


暂时就介绍这么多,其他的可以自己上网找找看哦,怎样,是不是学废了!

看到这里基本上同步异步操作都ok了;

但是这样还是避免不了要写去创建action,比如createAction,而且异步操作还需要中间件操作(虽然用起来还挺不错的

那有没有一种方法将异步操作和不需要写action一起解决掉,那这个时候可以借助@rematch/core,欲知后事如何请听下篇文章分解。