redux redux中间件

Redux中间件(Middleware)

简单使用

中间件:类似于插件,可以在不影响原本功能、并且不改动原本代码的基础上,对其功能进行增强。在Redux中,中间件主要用于增强dispatch函数。

实现Redux中间件的基本原理,是更改仓库中的dispatch函数。

Redux中间件书写:

  • 中间件本身是一个函数,该函数接收一个store参数,表示创建的仓库,该仓库并非一个完整的仓库对象,仅包含getState,dispatch。该函数运行的时间,是在仓库创建之后运行。
    • 由于创建仓库后需要自动运行设置的中间件函数,因此,需要在创建仓库时,告诉仓库有哪些中间件
    • 需要调用applyMiddleware函数,将函数的返回结果作为createStore的第二或第三个参数。
  • 中间件函数必须返回一个dispatch创建函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
*基础写法
* @param store 最终仓库
* @returns {function(*): function(*): void}
*/
function middleware1(store){
console.log("store",store);
return function(next){
console.log("next",next);//下一个中间件的dispatch
return function(action){
console.log("中间件1")
console.log("旧数据",store.getState());
console.log("action",action);
next(action);
console.log("新数据",store.getState());
console.log("")
}
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14

/**
* 箭头函数
* @param store
* @returns {function(*): function(*): void}
*/
const middleware2 = store=>next=>action=>{
console.log("中间件2")
console.log("旧数据",store.getState());
console.log("action",action);
next(action);
console.log("新数据",store.getState());
console.log("")
}
  • applyMiddleware函数,用于记录有哪些中间件,它会返回一个函数
    • 该函数用于记录创建仓库的方法,然后又返回一个函数

应用中间件:

1
2
3
4
// 参数一:reducer
// 参数二:仓库默认值
// 参数三:applyMiddleWare(中间件)
const store = createStore(reducer,10,applyMiddleware(middleware1,middleware2));
1
2
3
4
// 第一个函数的参数是使用的中间件
// 第二个函数的参数是创建仓库的方式
// 第三个参数的参数是创建仓库需要的参数
const store = applyMiddleware(middleware1,middleware2)(createStore)(reducer,10);

常用中间件

redux-logger

日志记录的中间件

一般写在使用中间件中最后一个,防止其他中间件干扰action

redux-thunk

处理副作用

thunk 允许action是一个带有副作用的函数,当action是一个函数被分发时,thunk会阻止action继续向后移交。

thunk会传递三个参数:

  1. dispatch: 来自于store.dispatch
  2. getState: 来自于store.getState
  3. extra: 来自于用户设置的额外参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";

function reducer(state, action) {
switch (action.type) {
case "add":
return state + 1;
case "del":
return state - 1;
default:
return state;
}
}

const action = {
type:"add",
}

function setTimeoutTestAction(dispatch,getState,extra){
console.log("dispatch",dispatch)
console.log("getState",getState)
console.log("extra",extra)
setTimeout(()=>{
dispatch(action);//三秒后触发action
},3000)
}

const store = createStore(reducer, 10, applyMiddleware(thunk, logger));

store.dispatch(setTimeoutTestAction)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//手写thunk 
/**
* 返回一个thunk中间件
* @param {*} extra 额外参数
* @returns 一个thunk中间件
*/
export function createThunkMiddleWare(extra){
return store=>next=>action=>{
if(typeof action === "function"){
return action(store.dispatch,store.getState,extra);
}
return next(action);
}
}

const thunk = createThunkMiddleWare();
thunk.withExtraArgument = createThunkMiddleWare;

export default thunk;

redux-saga

在saga任务中,如果yield了一个普通数据,saga不作任何处理,仅仅将数据传递给yield表达式(把得到的数据放到next的参数中),因此,在saga中,yield一个普通数据没什么意义。

saga需要你在yield后面放上一些合适的saga指令(saga effects),如果放的是指令,saga中间件会根据不同的指令进行特殊处理,以控制整个任务的流程。

每个指令本质上就是一个函数,该函数调用后,会返回一个指令对象,saga会接收到该指令对象,进行各种处理

一旦saga任务完成(生成器函数运行完成),则saga中间件一定结束

指令

指令前面必须有yield,以确保该指令的返回结果被saga控制

  • take指令:【阻塞】监听某个action,如果action发生了,则会进行下一步处理,take指令仅监听一次。yield得到的是完整的action对象
  • all指令:【阻塞】该函数传入一个数组,数组中放入生成器,saga会等待所有的生成器全部完成后才会进一步处理
  • takeEvery指令:不断的监听某个action,当某个action到达之后,运行一个函数。takeEvery永远不会结束当前的生成器
  • delay指令:【阻塞】阻塞指定的毫秒数
  • put指令:用于重新触发action,相当于dispatch一个action
  • call指令:【可能阻塞】用于副作用(通常是异步)函数调用
  • apply指令:【可能阻塞】用于副作用(通常是异步)函数调用
  • select指令:用于得到当前仓库中的数据
  • cps指令:【可能阻塞】用于调用那些传统的回调方式的异步函数
  • fork:用于开启一个新的任务,该任务不会阻塞,该函数需要传递一个生成器函数,fork返回了一个对象,类型为Task
  • cancel:用于取消一个或多个任务,实际上,取消的实现原理,是利用generator.return。cancel可以不传递参数,如果不传递参数,则取消当前任务线。
  • takeLastest:功能和takeEvery一致,只不过,会自动取消掉之前开启的任务
  • cancelled:判断当前任务线是否被取消掉了
  • race:【阻塞】竞赛,可以传递多个指令,当其中任何一个指令结束后,会直接结束,与Promise.race类似。返回的结果,是最先完成的指令结果。并且,该函数会自动取消其他的任务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { delay, put, take, takeEvery, } from "redux-saga/effects"
import { numberActionTypes ,decrease} from "../action/number"

function* testTakeEvery(){
yield delay(3000)
console.log("延时3秒输出")
yield put(decrease());//触发action
}

export default function* test() {
yield takeEvery(numberActionTypes.increase,testTakeEvery)
let action2 = yield take(numberActionTypes.decrease);
console.log("decrease",action2)
}