本文转载自微信公众号「 之家前端共享流」,作者张俊领。转载本文请联系之家前端共享流公众号。
Redux 是一个基于Flux架构的JavaScript 应用状态管理库,提供可预测性的状态管理方案。其中,middleware更是Redux中一个重要的概念,它存在使得Redux应用更加灵活、可扩展、可维护。本文中,我们将探讨 Redux middleware的运行机制和实现原理,最后带您轻松实现一个自己的middleware。无论你是初学者还是有一定经验的开发者,相信本文都能给你带来一些新的启示和技巧。让我们一起探索Redux middleware的魅力吧!
什么是Middleware
Redux middleware是一种可插拔的机制,用于在 Redux 的dispatch函数被调用后, redcer处理action之前,对action 进行拦截、变换、增强等操作。Redux middleware可以用于很多场景,例如:
异步操作:Redux本身是同步的,但是我们可以使用middleware来处理异步操作,例如发起网络请求,等待数据返回后再更新store;
日志:用于记录每个action的执行过程,以便于调试和分析;
认证和授权:可以拦截所有action,然后进行认证和授权,以确保只有授权用户可以执行某些操作。
middleware简化后的核心逻辑如下:
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对象。
使用Middleware
在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内部运行机制及原理剖析
我们通过上文的使用方式发现,middleware是通过createStore来增强和扩展原来的dispatch。下面我们就从createStore入手,逐步对middleware进行剖析:
createStore源码分析
//简化后的源码
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
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函数聚合的。
compose源码分析
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)));
}
如上所示,这段代码首先判断funcs数组的长度,如果长度为0,则直接返回一个函数;如果长度为1,则直接返回funcs[0];如果长度大于1,则使用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函数是非常实用的一个函数,通过它可以将任意多个函数组合在一起,实现更加灵活和有序的函数调用,增强了程序的复用性、可读性、可测性。
Middleware实现方式
在 Redux 应用中,我们可以使用多种方式来实现middleware。下面我们将介绍两种主要的实现方法:
基于洋葱模型实现
用过express、koa同学应该都知道它们也都有middleware概念,Redux middleware的实现和koa的洋葱模型的机制相似。Redux middleware在dispatch action和到达reducer之间提供第三方扩展点,这种实现方式的代码结构类似于洋葱,形成了一层层的包裹,每一层都可以执行一些操作,在每一层中可以对action进行处理。
基于装饰器实现
基于装饰器相对于基于洋葱模型更加直观和易于理解,但是它需要使用 ES7 中的装饰器语法,需要做一定的兼容性处理,这里不做过多阐述。
编写自定义Middleware
基于以上简要剖析,我们接下来可以进行开发属于自己的middleware。下文是一个最简单的middleware:
const loggerMiddleware = storeAPI => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', storeAPI.getState())
return result
}
这个简易版的logger负责在控制台中打印出当前动作的类型及当状态发生变化时打印出最新的状态。使用它可以帮助开发人员更快地发现应用中的异常。
为什么要使用storeAPI => next => action =>这种形式呢?
要回答这个问题我们可以先来看下Redux三大原则:
- 单一数据源
- state是只读
- 使用reducer纯函数进行更改
Redux middleware的storeAPI参数包含了整个Redux store的状态和dispatch方法,这保证了Redux应用中只有一个单一的数据源;middleware中的状态是只读的,不能被直接修改状态;Redux 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://redux.js.org/tutorials/fundamentals/part-4-store#middlewarehttps://github.com/reduxjs/redux
原文链接:https://mp.weixin.qq.com/s/JDM9GOYvkHui7lpwINxL8g