▼ 关注「之家前端共享」,获取更多技术干货 ▼
Redux
是一个基于Flux
架构的JavaScript 应用状态管理库,提供可预测性的状态管理方案。其中,middleware
更是Redux
中一个重要的概念,它存在使得Redux
应用更加灵活、可扩展、可维护。本文中,我们将探讨 Redux middleware
的运行机制和实现原理,最后带您轻松实现一个自己的middleware
。无论你是初学者还是有一定经验的开发者,相信本文都能给你带来一些新的启示和技巧。让我们一起探索Redux middleware
的魅力吧!Redux middleware
是一种可插拔的机制,用于在 Redux
的dispatch
函数被调用后, redcer处理action之前,对action 进行拦截、变换、增强等操作。Redux middleware
可以用于很多场景,例如:
异步操作
:Redux本身是同步的,但是我们可以使用middleware来处理异步操作,例如发起网络请求,等待数据返回后再更新store;
日志
:用于记录每个action的执行过程,以便于调试和分析;
认证和授权
:可以拦截所有action,然后进行认证和授权,以确保只有授权用户可以执行某些操作。
const middleware = store => next => action => {
// do something before dispatching the action
const result = next(action);
// do something after dispatching the action
return result;
};
通过以上代码可以看出middleware
本质上就是一个接受store
、next
、action
三个参数的函数。其中,store
是Redux
的store
对象,next
是dispatch
函数,action
是当前的action
对象。
在Redux
中使用middleware
非常简单,只需要在创建store的时候使用applyMiddleware
函数将middleware
应用到store上即可。
例如:
import { createStore, applyMiddleware } from 'redux'import rootReducer from './reducers'
import middleware1 from './middleware/middleware1'
import middleware2 from './middleware/middleware2'
const store = createStore(
rootReducer,
applyMiddleware(middleware1, middleware2)
)
applyMiddleware
函数将middleware1
、middleware2
应用到store上。这样,当我们调用 store.dispatch(action)
时,middleware就会被依次执行,直到reducer处理action。middleware
是通过createStore
来增强和扩展原来的dispatch
。下面我们就从createStore
入手,逐步对middleware
进行剖析://简化后的源码
import { Action } from './types/actions'
import { Reducer } from './types/reducers'
export function createStore<
S,
A extends Action,
Ext extends {} = {},
StateExt extends {} = {}
>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<S, A, StateExt> & Ext {
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState as StoreEnhancer<Ext, StateExt>
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error(
`Expected the enhancer to be a function. Instead, received: '${kindOf(enhancer)}'`
)
}
return enhancer(createStore)(
reducer,
preloadedState as PreloadedState<S>
) as Store<S, A, StateExt> & Ext
}
let currentReducer = reducer
let currentState = preloadedState as S
let currentListeners: Map<number, ListenerCallback> | null = new Map()
let nextListeners = currentListeners
let listenerIdCounter = 0
let isDispatching = false
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = new Map()
currentListeners.forEach((listener, key) => {
nextListeners.set(key, listener)
})
}
}
function getState(): S {
...
}
function subscribe(listener: () => void) {
...
let isSubscribed = true
ensureCanMutateNextListeners()
const listenerId = listenerIdCounter++
nextListeners.set(listenerId, listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error('...')
}
isSubscribed = false
ensureCanMutateNextListeners()
nextListeners.delete(listenerId)
currentListeners = null
}
}
function dispatch(action: A) {
...
}
dispatch({ type: ActionTypes.INIT } as A)
const store = {
dispatch: dispatch as Dispatch<A>,
subscribe,
getState
} as unknown as Store<S, A, StateExt> & Ext
return store
}
从以上代码,createStore
方法接收三个参数:reducer
、preloadedState
和enhancer
。如果传入了enhancer
则使用enhancer来增强store(实际上是通过重写createStore来增强dispatch),否则就返回一个包含getState、dispatch
和subscribe方法
的store对象。其中,这里的第三个参数enhancer就是我们下文要分析的applyMiddleWare。
//简化后的源码
export default function applyMiddleware(
...middlewares: Middleware[]
): StoreEnhancer<any> {
return createStore =>
<S, A extends AnyAction>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>
) => {
const store = createStore(reducer, preloadedState)
let dispatch: Dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI: MiddlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
middleware
柯里化函数第一层来为每个middleware函数提供getState
和dispatch
;再通过compose
将所有middleware
串联起来形成一个函数链,从而实现对Redux数据的拦截和处理,并最终返回一个增强版的dispatch。我们看到在applyMiddleWare
中compose
是核心逻辑,下面我们具体分析下compose是如何进行middleware函数聚合的。export default function compose(...funcs: Function[]) {
if (funcs.length === 0) {
// infer the argument type so it is usable in inference down the line
return <T>(arg: T) => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
reduce
方法通过把后一个的middleware
的结果当成参数传递给下一个middleware
的方式将funcs数组中的函数依次组合起来。这里的func也就是接收next
即dispatch
作为参数的middleware
柯里化函数第二层,func执行后会返回一个新函数action => next(action)
。最终compose
返回一个新函数,并按照从右到左的顺序依次调用每个func进行处理,这个函数就是增强版的dispatch
。接下来,我们可以用“把大象放冰箱”这个哲理题作为一个示例,来继续加深对compose
函数的理解:
function putElephantInFridge(){
console.log('打开冰箱门');
console.log('把大象放进去');
console.log('关上冰箱门');
}
function openFridgeDoor() {
console.log('打开冰箱门');
}
function putSomethingInFridge(something) {
console.log(`把${something}放进去`);
}
function closeFridgeDoor() {
console.log('关上冰箱门');
}
const putInFridge = (something)=>compose(closeFridgeDoor,()=>{putSomethingInFridge(something)},openFridgeDoor)();
const putInFridgeAndNotClose = (something)=>compose(()=>{putSomethingInFridge(something)},openFridgeDoor)();
putInFridge('牛奶'); // 打开冰箱门 把牛奶放进去 关上冰箱门
putInFridgeAndNotClose('苹果'); // 打开冰箱门 把苹果放进去
在上面的代码中,我们使用compose
函数将三个单独的函数组合成了一个函数putInFridge
,该函数接收一个参数something,并依次执行三个步骤,最终将something放进了冰箱中。另外,我们也可以将其中两个函数组合成函数putInFridgeAndNotClose
。由上我们看到,compose函数是非常实用的一个函数,通过它可以将任意多个函数组合在一起,实现更加灵活和有序的函数调用,增强了程序的复用性、可读性、可测性。
基于洋葱模型实现
express
、koa
同学应该都知道它们也都有middleware概念,Redux middleware
的实现和koa的洋葱模型的机制相似。Redux middleware
在dispatch action
和到达reducer
之间提供第三方扩展点,这种实现方式的代码结构类似于洋葱,形成了一层层的包裹,每一层都可以执行一些操作,在每一层中可以对action进行处理。
middleware
。下文是一个最简单的middleware
:const loggerMiddleware = storeAPI => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', storeAPI.getState())
return result
}
为什么要使用storeAPI => next => action =>这种形式呢?
要回答这个问题我们可以先来看下Redux三大原则:
dispatch
方法,这保证了Redux应用中只有一个单一的数据源;middleware
中的状态是只读的,不能被直接修改状态;middleware中的next函数它接收一个动作作为参数,并返回一个新的函数。因此,采用这种形式正是更好的遵循Redux的设计原则,确保Redux应用程序的可预测性、可维护性和可扩展性。另外,在Redux社区中也有对使用这种形式的不同声音,他们认为“there is essentially no need to split the arguments into different function calls”、“minor change could promote authors' familiarity and understanding, thus encourage the development of additional middleware to Redux”,关于这块您可以自行扩展阅读。middleware是 Redux 应用中的一个重要概念,Redux middleware的原理是基于Redux原则和函数式编程思想,通过函数柯里化和函数组合来实现对dispatch的增强,使得在数据流传递过程中可以插入一些自定义的操作。最后,希望本文能够帮助读者加深对middleware原理的理解,助您开发出更加稳定、高效的react应用。
参考
https://github.com/reduxjs/redux
点赞
和在看
是最大的支持⬇️❤️⬇️微信扫一扫
关注该公众号