Redux5源码解析: applyMiddleware——源码初探

422 阅读2分钟

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战

Redux可以说是一个典型的小而精的lib,源码量及api数量都不多,但设计却十分巧妙。本系列将深入Redux5源码,探究其实现与设计。

applyMiddleware

Redux中的核心apicreateStore可以支持三个参数:createStore(reducer, [preloadedState], [enhancer]),第三个参数是store增强器,而Redux自带的唯一个enhancer便是applyMiddleware,因此在解析createStore之前,先来看一看applyMiddleware

源码实现

function applyMiddleware(
  ...middlewares
) {
  return (createStore) =>(
      reducer,
      preloadedState
    ) => {
      const store = createStore(reducer, preloadedState)
      let dispatch = () => {
        throw new Error(...)
      }

      const middlewareAPI = {
        getState: store.getState,
        dispatch: (action, ...args) => dispatch(action, ...args)
      }
      const chain = middlewares.map(middleware => middleware(middlewareAPI))
      dispatch = compose(...chain)(store.dispatch)

      return {
        ...store,
        dispatch
      }
    }
}

从源码可以看出,applyMiddleware的参数,即每一个中间件都是一个函数,并且这些函数都被compose组合了起来。这些中间件函数的类型定于如下

export interface Middleware<
  _DispatchExt = {}, 
  S = any,
  D extends Dispatch = Dispatch
> {
  (api: MiddlewareAPI<D, S>): (
    next: D
  ) => (action: D extends Dispatch<infer A> ? A : never) => any
}

这些中间件函数,接收的参数是getState(虽然也有dispatch,但其实不可用),返回的也是一个函数:(next)=>(action)=>any,先称它为fn吧。chain数组中的每个成员都是这样的一个fn。

fn函数能通过闭包获取到getState,因此可以记录state的快照。而next参数其实是一个dispatch(初始是store.dispatch,之后是前一个fn返回的(action)=>any。这样就巧妙的实现了一个洋葱圈)这些fn函数又被compose组合为一个新的dispatch,从applyMiddleware中返回出来,成为store增加的dispatch。

因此当store调用dispatch的时候,fn可以通过action拿到dispatch的参数,通过getState获取前后的state,实现自己的中间件逻辑。

举个例子

以官网的log插件为例,可以更好地理解源码的运行过程。

import { createStore, applyMiddleware } from 'redux'
import todos from './reducers'
function logger({ getState }) {
    return next => action => {
        console.log('will dispatch', action)

        const returnValue = next(action)
        console.log('state after dispatch', getState())

        return returnValue
    }
}

const store = createStore(todos, ['Use Redux'], applyMiddleware(logger))

store.dispatch({
    type: 'ADD_TODO',
    text: 'Understand the middleware'
})
// will dispatch: { type: 'ADD_TODO', text: 'Understand the middleware' }
// state after dispatch: [ 'Use Redux', 'Understand the middleware' ]

未完待续

本文浅尝applyMiddleware的源码,梳理其中几个函数的关系。但applyMiddleware的独特魅力在于以极少的代码便实现了一个洋葱圈模型。

下一篇将解剖洋葱圈,深入探究其实现原理。