1.redux-saga是用来干嘛的
- redux-saga 是redux 的中间件,为redux提供额外的操作。
- 在reducers 中的所有操作都是同步并且是纯粹的,Reducer是纯函数。纯函数就是不会产生对外部产生副作用,你传给他什么,他就给你吐出来什么。
- 但是在实际开发中,我们希望请求一些异步函数且不纯粹的操作(改变外部的状态),这写在函数式编程中称为副作用函数。
2.redux-saga工作原理
- redux-saga采用Generator函数来 yield effect
- Generator 函数的作用是可以暂停执行,再次执行的时候从上次暂停的地方继续执行
- Effect 是一个简单的对象,该对象包含了一些给 middleware 解释执行的信息。
- 你可以通过使用 effects API 如 fork,call,take,put,cancel 等来创建 Effect。
3.redux-saga分类
- worker-saga做工作,如调用API、进行异步请求、获取异步封装结果
- watcher-saga 监听被dispatch的actions,当接受到action或者知道其他被触发的时候,调用worker执行任务。
- root-saga 立即启动sage的唯一入口
4.使用 redux-saga
npx create-react-app react-redux-saga
pnpm i redux redux-saga react-redux
import { createStore } from 'redux'
import { applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import { userReducer } from './reducers'
import { rootSaga } from './saga'
let sagaMiddleWare = createSagaMiddleware()
let store = applyMiddleware(sagaMiddleWare)(createStore)(userReducer)
sagaMiddleWare.run(rootSaga)
export default store;
export const INCREMENT_ASYNC = 'INCREMENT_ASYNC';
export const INCREMENT = 'INCREMENT';
import * as types from './actions-type'
const initData = {
userInfo: {},
number: 0,
}
const userReducer = (userData = initData, action) => {
switch (action.type) {
case types.INCREMENT_ASYNC:
return { ...userData, number: userData.number + 1 }
default:
return userData;
}
}
export {
userReducer
}
import { put, take } from 'redux-saga/effects';
import * as types from './actions-type';
export function* rootSaga() {
yield take(types.INCREMENT_ASYNC);
console.log('已经达到最大值');
}
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import { Provider } from 'react-redux'
import store from './redux-saga';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
import logo from './logo.svg';
import './App.css';
import { useSelector, useDispatch } from 'react-redux';
import { INCREMENT_ASYNC } from "./redux-saga/actions-type";
import { useEffect } from "react";
function App() {
const dispatch = useDispatch();
const data = useSelector((state) => state.number);
console.log(data)
useEffect(() => {
dispatch({ type: INCREMENT_ASYNC });
}, [dispatch]);
return (
<div className="App">
{data}
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
5.Redux-saga 原理
5.1.实现take put原理
- take 主要是用于监听一个特定的action的effect,会等待指定的action被dispatch,然后执行后续的操作
put 是一个 effect,用于触发一个 Redux action,类似于 dispatch。它的主要作用是在 saga 中发出 action,通常用于更新 Redux store 或触发其他的副作用
- redux-saga/index.ts
export default function createSagaMiddleware() {
function createChannel() {
let listener={};
function subscribe(actionType,cb) {
listener[actionType]=cb;
}
function publish(action) {
if (listener[action.type]) {
let temp=listener[action.type];
delete listener[action.type];
temp(action);
}
}
return {subscribe,publish}
}
let channel=createChannel();
function sagaMiddleware({getState,dispatch}) {
function run(generator) {
let it=generator();
function next(action) {
let {value:effect,done} = it.next(action);
if (!done) {
switch (effect.type) {
case 'TAKE':
channel.subscribe(effect.actionType,next);
break;
case 'PUT':
dispatch(effect.action);
next();
break;
default:
}
}
}
next();
}
sagaMiddleware.run=run;
return function (next) {
return function (action) {
channel.publish(action);
next(action);
}
}
}
return sagaMiddleware;
}
export function take(actionType) {
return {
type: 'take',
actionType
}
}
export function put(action) {
return {
type: 'put',
action
}
}
- redux-saga/effects-actions.ts
export const TAKE = 'TAKE'
export const PUT = 'PUT'
5.2.支持产出 Iterator
export default function createSagaMiddleware() {
function createChannel() {
let listener={};
function subscribe(actionType,cb) {
listener[actionType]=cb;
}
function publish(action) {
if (listener[action.type]) {
let temp=listener[action.type];
delete listener[action.type];
temp(action);
}
}
return {subscribe,publish};
}
let channel=createChannel();
function sagaMiddleware({getState,dispatch}) {
function run(generator) {
+ let it= typeof generator == 'function'? generator():generator;
function next(action) {
let {value:effect,done} = it.next(action);
if (!done) {
+ if (typeof effect[Symbol.iterator]=='function') {
+ run(effect);
+ next();
+ } else {
switch (effect.type) {
case 'TAKE':
channel.subscribe(effect.actionType,next);
break;
case 'PUT':
dispatch(effect.action);
next();
break;
default:
}
}
}
}
next();
}
sagaMiddleware.run=run;
return function (next) {
return function (action) {
channel.publish(action);
next(action);
}
}
}
return sagaMiddleware;
}
5.3.支持 takeEvery fork
takeEvery 是一个用来监听并处理所有指定类型 action 的 effect。它的主要作用是每当指定的 action 被 dispatch 时,都启动一个新的 saga 执行,而不会阻塞其他操作。和 take 不同,takeEvery 会启动多个并行的任务(即使前一个任务还没有完成)。
- 处理并发的异步操作:当多个相同的 action 被 dispatch 时,
takeEvery 会并行地启动多个任务来处理这些 action,而不需要等待前一个任务完成。
fork 是用来创建一个新的并行任务,并立即执行,但是不会阻塞当前的执行流。
- redux-saga/effects.ts
import { FORK, PUT, TAKE } from "./effects-actions"
export function take(actionType) {
return {
type: TAKE,
actionType
}
}
export function put(action) {
return {
type: PUT,
action
}
}
export function fork(task) {
return {
type: FORK,
task
}
}
export function* takeEvery(actionType, task) {
yield fork(function* () {
while (true) {
yield take(actionType);
yield task();
}
});
}
import { FORK } from "./effects-actions";
export default function createSagaMiddleware() {
function createChannel() {
let listener = {};
function subscribe(actionType, cb) {
listener[actionType] = cb;
}
function publish(action) {
if (listener[action.type]) {
let temp = listener[action.type];
delete listener[action.type];
temp(action);
}
}
return { subscribe, publish }
}
let channel = createChannel();
function sagaMiddleware({ getState, dispatch }) {
function run(generator) {
let it = generator();
function next(action?) {
let { value: effect, done } = it.next(action);
if (!done) {
switch (effect.type) {
case 'TAKE':
channel.subscribe(effect.actionType, next);
break;
case 'PUT':
dispatch(effect.action);
next();
break;
case FORK:
run(effect.task);
next();
break;
default:
}
}
}
next();
}
sagaMiddleware.run = run;
return function (next) {
return function (action) {
channel.publish(action);
next(action);
}
}
}
return sagaMiddleware;
}
5.4.支持Promise
import { FORK } from "./effects-actions";
export default function createSagaMiddleware() {
function createChannel() {
let listener = {};
function subscribe(actionType, cb) {
listener[actionType] = cb;
}
function publish(action) {
if (listener[action.type]) {
let temp = listener[action.type];
delete listener[action.type];
temp(action);
}
}
return { subscribe, publish }
}
let channel = createChannel();
function sagaMiddleware({ getState, dispatch }) {
function run(generator) {
let it = generator();
function next(action?) {
let { value: effect, done } = it.next(action);
if (!done) {
if (typeof effect[Symbol.iterator] === 'function') {
run(effect)
next()
} else if (effect.then) {
effect.then(next)
} else {
switch (effect.type) {
case 'TAKE':
channel.subscribe(effect.actionType, next);
break;
case 'PUT':
dispatch(effect.action);
next();
break;
case FORK:
run(effect.task);
next();
break;
default:
}
}
}
}
next();
}
sagaMiddleware.run = run;
return function (next) {
return function (action) {
channel.publish(action);
next(action);
}
}
}
return sagaMiddleware;
}
5.5.支持call
- 执行异步操作并获取结果:
call 用来调用异步函数,等待结果,并且可以处理返回值或错误。
- 同步处理异步操作:通过
call,你可以把异步操作的处理方式转变为同步流程,使代码结构更清晰。
- redux-saga/effects.ts
export function call(fn, ...args) {
return {
type: CALL,
fn,
args
}
}
- redux-saga/effects-actions.ts
import { CALL, FORK, PUT, TAKE } from "./effects-actions";
export default function createSagaMiddleware() {
function createChannel() {
let listener = {};
function subscribe(actionType, cb) {
listener[actionType] = cb;
}
function publish(action) {
if (listener[action.type]) {
let temp = listener[action.type];
delete listener[action.type];
temp(action);
}
}
return { subscribe, publish }
}
let channel = createChannel();
function sagaMiddleware({ getState, dispatch }) {
function run(generator) {
let it = generator();
function next(action?) {
let { value: effect, done } = it.next(action);
if (!done) {
if (typeof effect[Symbol.iterator] === 'function') {
run(effect)
next()
} else if (effect.then) {
effect.then(next)
} else {
switch (effect.type) {
case TAKE:
channel.subscribe(effect.actionType, next);
break;
case PUT:
dispatch(effect.action);
next();
break;
case FORK:
run(effect.task);
next();
break;
case CALL:
effect.fn(...effect.args).then(next);
break;
default:
}
}
}
}
next();
}
sagaMiddleware.run = run;
return function (next) {
return function (action) {
channel.publish(action);
next(action);
}
}
}
return sagaMiddleware;
}
5.6.支持cps
cps 用于处理需要通过回调来获取结果的异步操作。
- redux-saga/effects.js
export function cps(fn, ...args) {
return {
type: CPS,
fn,
args
}
}
import { CALL, CPS, FORK, PUT, TAKE } from "./effects-actions";
export default function createSagaMiddleware() {
function createChannel() {
let listener = {};
function subscribe(actionType, cb) {
listener[actionType] = cb;
}
function publish(action) {
if (listener[action.type]) {
let temp = listener[action.type];
delete listener[action.type];
temp(action);
}
}
return { subscribe, publish }
}
let channel = createChannel();
function sagaMiddleware({ getState, dispatch }) {
function run(generator) {
let it = generator();
function next(action?) {
let { value: effect, done } = it.next(action);
if (!done) {
if (typeof effect[Symbol.iterator] === 'function') {
run(effect)
next()
} else if (effect.then) {
effect.then(next)
} else {
switch (effect.type) {
case TAKE:
channel.subscribe(effect.actionType, next);
break;
case PUT:
dispatch(effect.action);
next();
break;
case FORK:
run(effect.task);
next();
break;
case CALL:
effect.fn(...effect.args).then(next);
break;
case CPS:
effect.fn(...effect.args, next);
break;
default:
}
}
}
}
next();
}
sagaMiddleware.run = run;
return function (next) {
return function (action) {
channel.publish(action);
next(action);
}
}
}
return sagaMiddleware;
}
5.7.支持all
all 是一个效果方法,用于并行执行多个 saga(生成器函数)。它的作用是让多个异步操作(或多个 saga)并行运行,而不需要等待每个操作逐个完成
- redux-saga/effects.ts
export function all(fns) {
return {
type: ALL,
fns
}
}
import { ALL, CALL, CPS, FORK, PUT, TAKE } from "./effects-actions";
export default function createSagaMiddleware() {
function createChannel() {
let listener = {};
function subscribe(actionType, cb) {
listener[actionType] = cb;
}
function publish(action) {
if (listener[action.type]) {
let temp = listener[action.type];
delete listener[action.type];
temp(action);
}
}
return { subscribe, publish }
}
let channel = createChannel();
function times(cb, total) {
let count = 0;
return function () {
if (++count === total) {
cb();
}
}
}
function sagaMiddleware({ getState, dispatch }) {
function run(generator, callback?) {
let it = generator();
function next(action?) {
let { value: effect, done } = it.next(action);
if (!done) {
if (typeof effect[Symbol.iterator] === 'function') {
run(effect)
next()
} else if (effect.then) {
effect.then(next)
} else {
switch (effect.type) {
case TAKE:
channel.subscribe(effect.actionType, next);
break;
case PUT:
dispatch(effect.action);
next();
break;
case FORK:
run(effect.task);
next();
break;
case CALL:
effect.fn(...effect.args).then(next);
break;
case CPS:
effect.fn(...effect.args, next);
break;
case ALL:
let fns = effect.fns;
let done = times(next, fns.length);
for (let i = 0; i < fns.length; i++) {
let fn = fns[i];
run(fn, done);
}
break;
default:
}
}
} else {
callback && callback();
}
}
next();
}
sagaMiddleware.run = run;
return function (next) {
return function (action) {
channel.publish(action);
next(action);
}
}
}
return sagaMiddleware;
}
5.8.取消任务
export function cancel(task) {
return {
type: CANCEL,
task
}
}
import { ALL, CALL, CANCEL, CPS, FORK, PUT, TAKE } from "./effects-actions";
export default function createSagaMiddleware() {
function createChannel() {
let listener = {};
function subscribe(actionType, cb) {
listener[actionType] = cb;
}
function publish(action) {
if (listener[action.type]) {
let temp = listener[action.type];
delete listener[action.type];
temp(action);
}
}
return { subscribe, publish }
}
let channel = createChannel();
function times(cb, total) {
let count = 0;
return function () {
if (++count === total) {
cb();
}
}
}
function sagaMiddleware({ getState, dispatch }) {
function run(generator, callback?) {
let it = generator();
function next(action?) {
let { value: effect, done } = it.next(action);
if (!done) {
if (typeof effect[Symbol.iterator] === 'function') {
run(effect)
next()
} else if (effect.then) {
effect.then(next)
} else {
switch (effect.type) {
case TAKE:
channel.subscribe(effect.actionType, next);
break;
case PUT:
dispatch(effect.action);
next();
break;
case FORK:
let newTask = effect.task();
run(newTask);
next(newTask);
break;
break;
case CALL:
effect.fn(...effect.args).then(next);
break;
case CPS:
effect.fn(...effect.args, next);
break;
case ALL:
let fns = effect.fns;
let done = times(next, fns.length);
for (let i = 0; i < fns.length; i++) {
let fn = fns[i];
run(fn, done);
}
break;
case CANCEL:
effect.task.return('over');
break;
default:
}
}
} else {
callback && callback();
}
}
next();
}
sagaMiddleware.run = run;
return function (next) {
return function (action) {
channel.publish(action);
next(action);
}
}
}
return sagaMiddleware;
}