前言~
在正式讲redux中间件之前,我们先讲讲怎么应用中间件,以及自定义中间件怎么编写,再深入
applyMiddleWare原理,看看是如何处理中间件。
应用中间件
redux这个库给我门提供了应用中间件的函数 applyMiddleWare,专门用于应用中间件函数。
- 自定义中间件
/**
* 自定义中间件函数,store 为 store仓库中的store
* @param {*} store
* @returns 返回一个dispatch 创建函数
*/
const logger = store => {
// 返回一个dispatch创建函数
return (next) => {
console.log("logger 中间件运行了 ===", next)
// 返回一个dispatch函数
return (action) => {
console.log("action 中间件运行了 ===", action)
next(action)
}
}
}
创建一个 logger函数,接受一个store,该值仓库中的 store用于得到仓库中的dispatch、getState ...中的方法。返回值为一个 dispatch创建函数,该函数接收一个经过下一个 中间件 包装过后的 dispatch ,我们命名为 next,返回值为一个真正的 dispatch 函数,与仓库中件 dispatch 合并。
读到这,你可能会产生很多疑问,next参数是怎么经过下一个中间件包装的呢?具体的包装过程是怎么实现的呢?咱们移步下文~ applyMiddleWare 源码部分
- applyMiddleWare 具体使用中间件:
import {createStore, applyMiddleware, bindActionCreators} from "redux";
const logger = store => {
// 返回一个dispatch创建函数
return (next) => {
console.log("logger 中间件运行了 ===", next)
return (action) => {
next(action)
}
}
}
const logger1 = store => {
// 返回一个dispatch创建函数
return (next) => {
console.log("logger1 dispatch中间件运行了 ===", next)
// 返回一个dispatch函数
return (action) => {
console.log("logger1 action 中间件运行了 ===", action)
next(action)
}
}
}
const store = applyMiddleware(logger,logger1)(createStore)(reducer);
applyMiddleWare 函数传入了两个logger、logger1自动义的中间件,然后返回了一个函数,函数执行传入了 createStore创建仓库的函数,然后又返回一个函数,执行传入了一个reducer函数。柯里化函数应用典范!!
applyMiddleWare 原理
import compose from "./compose";
/**
*
* @param {...any} middlewares 得到一个中间件数组
* @returns 返回一个创建仓库的函数
*/
export default function ApplyMiddleware(...middlewares) {
/**
* 返回一个 仓库创建函数
*/
return function (createStore) {
/**
* 返回一个接收 reducer函数
*/
return function(reducer,defaultState) {
let store = createStore(reducer,defaultState);
let dispatch = () => { throw new Error("目前还不能使用dispatch") };
let middleWareApi = {
dispatch: (action) => dispatch(action), // dispatch 指向一个函数,然后调用新的dispatch,联动变化,保持引用地址一致。
getState: store.getState
}
//根据中间件数组,得到一个dispatch创建函数的数组
let dispatchCreators = middlewares.map( middleWare => middleWare(middleWareApi))
dispatch = compose(...dispatchCreators)(store.dispatch);
console.log("----dispatch-----", dispatch)
return {
...store,
dispatch,
}
}
}
}
ApplyMiddleware 返回一个接收创建仓库的函数,用于接收仓库的创建函数,执行该函数,返回一个新的函数,用于接收 reducer、defaultState, reducer创建函数和仓库默认值。在该函数内部,通过createStore函数创建了一个仓库,创建了一个初始化 dispatch 函数。
- 细讲
middleWareApi该 api 是作为给每个中间件 store 传递dispatch、getState两个方法,你可能疑惑,为什么dispatch属性不直接赋值store.dispatch,而是要创新创建一个函数?
该操作是为了解决thunk处理副作用中间件的问题。当我们使用thunk的时候, 可以在action进行副作用处理,action返回可以是一个 函数,thunk中间件在处理的时候,判断 action为一个函数,直接 把 action 当作函数执行,然后把 最新包装的dispatch传递给 action这个函数。 如果不使用函数,而是使用 store.dispatch,则当dispatch经过包装更新后,middleWareApi 的 dispatch的引用还是原来 store.dispatch的引用。使用函数就是为了 在每次dispatch 时都能更新到最新的dispatch,去触发action。
//根据中间件数组,得到一个dispatch创建函数的数组
let dispatchCreators = middlewares.map( middleWare => middleWare(middleWareApi))
执行每个中间件函数, 得到一个每个中间件函数,创建dispatch函数的数组。
- compose
从后向前组包装
dispatch函数,该函数接收dispatch数组,把多个 dispatch 创建函数执行 返回一个单一dispatch函数,compose返回一个函数,接收store.dispatch参数。 compose 函数的具体实现:
export default function compose(...dispatchCreators) {
// if (dispatchCreators.length === 0) {
// return args => args; //如果没有要组合的函数,则返回的函数原封不动的返回参数
// }
// else if (dispatchCreators.length === 1) {
// //要组合的函数只有一个
// return dispatchCreators[0];
// }
// return dispatchCreators.reduce((a,b) => (...regs) => a(b(...regs)))
return function(...args) {
let lastReturn = null;
for(let i = dispatchCreators.length - 1; i >= 0 ; i --) {
const dispatchCreate = dispatchCreators[i]
if(i === dispatchCreators.length - 1) { // 表示为最后一个中间件的dispatch创建函数
lastReturn = dispatchCreate(...args); // 最后一个 中间件把 dispatch函数返回 交给 前面一个中间件
}else {
lastReturn = dispatchCreate(lastReturn);
}
}
return lastReturn
}
}
compose 函数接收一个 dispatch创建函数的数组,返回一个函数,接收dispatch参数,从后往前遍历 dispatch创建函数,把仓库原始的 dispatch函数,传递到dispatch创建函数中, lastReturn 变量接收一个新的 dispatch 函数,把该函数当作参数,移交给下一个 中间件的 dispatch创建函数。
我们回到 logger 自定义中间件
const logger = store => {
// 返回一个dispatch创建函数
return (next) => {
console.log("logger 中间件运行了 ===", next)
// 返回一个dispatch函数
return (action) => {
console.log("action 中间件运行了 ===", action)
next(action)
}
}
}
// return dispatchCreators.reduce((a,b) => (...regs) => a(b(...regs)))
如果 next 不执行,则后面的中间件不会执行。
为什么呢?
我们再来细看 dispatch 组合函数的过程--- dispatchCreators.reduce((a,b) => (...regs) => a(b(...regs))), 对与 applyMiddleWare 来说, a() 就相当于 第一个logger 中间件执行,然后传入 b(...regs), 就相当于是 loggeer中的 next, 从中间件本质来说就是 logger1 这个中间件,所以 next不去执行的化,logger后的中间件 都没有办法去执行.
我想上面一段描述,是大家都不太清楚的,以上都是个人目前对于 applyMiddleWare的理解,还希望大家能指出其中的不足 ~ ~ 感谢
redux-thunk
redux-thunk中间件的作用是 我们可以action中进行副作用处理,在不使用 thunk时,action只能时一个平面对象。
在action 中进行副作用处理:
/**
* 获取用户数据的副作用函数
*/
export function fetchUsers() {
// dispatch 是thunk中间件传递过来的参数,还有 getState、extra 额外的参数
return async function (dispatch) {
dispatch(createSetIsloading(true)); // 获取用户前, 显示正在加载数据
const users = await getAllStudents(); // 发送ajax请求获取学生数据
// 获取设置学生用户的action
const action = createSetUser(users);
dispatch(action); // 通过dispatch 向仓库分发数据
dispatch(createSetIsloading(false)) // 关闭正在加载数据
}
}
action 创建函数返回一个函数,该函数交给thunk中间件进行处理,在thunk内部判断如果是函数,则直接执行该函数,把仓库原始的 dispatch、getState当参数传给action函数。
以上就是在 thunk中进行副作用处理,在thunk内部是怎么实现的呢?
- thunk 源码
function createThunkMiddleware(extra) {
// 返回一个 redux 中间件函数
return store => next => action => {
if(typeof action === 'function') {
action(store.dispatch, store.getState, extra);
}else {
next(action);
}
}
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware
export default thunk
返回一个中间件函数,判断 action 的类型,如果是函数就直接调用该函数,不是则移交给下一个中间件进行处理。
redux-promise
redux-promise 允许action是一个promise 类型,如果action 是一个 promise,则会等待 promise完成, 将resolve的结果,作为action,去触发 reducer 改变仓库中的数据。
- action为 promise
/**
* 获取用户数据的副作用函数
*/
export function fetchUsers() {
return new Promise((resolve) => {
const users = getAllStudents(); // 发送ajax请求获取学生数据
// 获取设置学生用户的action
const action = createSetUser(users);
resolve(action)
})
}
- payload 为promise
如果
action不是一个promise, 则判断action中的payload是不是promise,如果是,等待promise完成,然后将.thenresolve 中的返回值作为action的payload。
/**
* 获取用户数据的副作用函数
*/
export function fetchUsers() {
return {
type: SETUSERS,
payload: getAllStudents().then( res => res)
}
}
redux-saga
使用saga时,在初开始的时候会启动一个 saga任务, saga任务本质:是一个生成器函数,saga会保存生成器函数创建的生成器,在saga内部控制该生成器的运行。
saga为任务提供大量的功能以供使用, 这些任务都是以指令的形式出现而且出现在yield的位置,因此可以被saga中间件控制它的执行。
本质,通过任务影响 action
saga本质上是运行一个saga任务, saga任务是一个生成器函数,saga的功能都是以指令的形式出现,被放在 yield 关键字后面,因此,被sage中间件控制执行。
图解:
注意:
指令前面必须使用 yield
在saga任务中,如果yield一个普通数据,saga不做任何处理,会将数据传递给yield表达式(把数据放到 next方法中),在saga中 yield一个普通数据没意义。
saga 需要你在yield后,执行一些 saga指令, saga中间件会对不同的指令进行不同的特殊处理,控制任务流程。
saga指令本质上就是函数,函数调用后,会返回一个指令对象,saga会接收该指令对象,进行不同的处理。
- take指令:【阻塞】会阻塞saga任务不完成,监听某个action,如果action被dispatch了,该指令完成,则会进行下一步处理,
take指令只监听一次,yield得到的是完整得 action对象。
let result = yield take(actionTypes.asyncIncrease);
// 监控 asyncIncrease 被dispatch触发后,触发指令完成
- all指令:【阻塞】该函数传入一个数组,数组成员都是
生成器,all指令会等待所有的生成器全部完成后才进行下一步处理
let result = yield all([getStudent(), studentTast()])
- takeEvery指令:不断监听某个
action,某个action被dispatch后,运行一个函数。takeEvery永远不会结束当前的生成器。
function* () {
yield takeEvery(actionTypes.fechStudent, fetchStudent);
console.log("正在监听action")
}
- delay指令:【阻塞】指定延迟的毫秒数
- put指令:相当于dispatch一个action,用于重新触发一个action,
- call指令:【可能阻塞】调用函数,如果调用的是一个promise,则会等待完成
- 如果需要指定
this指向,则call(['this指向', 调用的函数], '后续参数...') call({context:"", fn: 调用的函数})
- 如果需要指定
- apply指令:调用函数(通常是异步的),
- 绑定指定this,apply('this指向','调用函数','[参数]')
-select指令:用于得到当前仓库中的数据
const state = yield select()
返回指定仓库中的数据
const state = yield select( store => store.student.condition)
- cps指令: 【可能阻塞】用于调用那些传统回调模式的异步的callback
重点
saga中,当saga yield之后得到的结果是一个Promise对象时,他会自动等待 Promise 完成,然后把resolve的值传作为 下一次next调用的参数。
如果Promise对象被拒绝(执行的是reject回调),saga会使用 generator.throw 抛出一个error