实现Redux的中间件功能

1,495 阅读6分钟

一、背景

了解Redux并实现简易的Redux中,我们自己手写了一个简单的Redux。但目前该版本只支持同步操作,对于异步操作,Action为函数的情况,这个版本并不支持,但实际上Redux原本也不支持异步操作的能力,也不能处理函数,他也要借助中间件的实现。(参考Redux Essentials, Part 5: Async Logic and Data Fetching | Redux

什么是中间件

Enhancers are powerful because they can override or replace any of the store's methods: dispatch, getState, and subscribe.

Middleware类似于中间层的意思,这是个广义的名词。当我们的上层应用和下层应用需要进行数据传递/通信的时候,有时候存在我们需要对请求数据、数据处理、日志记录,甚至自定义等操作。这个时候,这个功能放在上、下层都不合适,这个时候,如果有中间层去进行这个工作,那么就不会影响其他层的逻辑了。

Redux就是用了middleware这个概念,让我们去自定义我们的dispatch,实际上就是加多一层修饰器去包裹store。下图是个例子,应该能大概了解中间件的流程了吧。

二、Redux使用中间件

上面我们简单的讲了一下中间件的概念,也讲了redux中间件的简单思路,那么Redux是提供了什么接口让我们把中间件进行嵌入的呢?

答案就是applyMiddleware(参考Redux Fundamentals, Part 4: Store | Redux)

简单使用

自定义中间件

注意,redux中的中间件是有规范的,我们可以参考Redux Fundamentals, Part 4: Store | Redux,下面我们按照官方文档自定义loggerMiddleware

const loggerMiddleware = storeAPI => next => action => {
  console.log('dispatching', action)
  let result = next(action)
  console.log('next state', storeAPI.getState())
  return result
}

使用applyMiddlewares引入

import { createStore, Action, applyMiddleware } from "redux";
import loggerMiddleware from "../middlewares/logMiddleware";

function counterReducer(state = 0, action: Action<'incremented' | 'decremented'>) {
  switch (action.type) {
    case 'incremented':
      return state + 1;
    case 'decremented':
      return state - 1;
    default:
      return state;
  }
}

const store = createStore(counterReducer, applyMiddleware(loggerMiddleware));

export default store;

效果

我们可以看我们的中间件也生效了。

使用第三方中间件

redux目前也有很多第三方的中间件,这里,我们使用redux-thunk以及redux-logger来实验下。

npm install redux-thunk redux-logger
import { createStore, Action, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import logger from "redux-logger";

function counterReducer(state = 0, action: Action<'incremented' | 'decremented'>) {
  switch (action.type) {
    case 'incremented':
      return state + 1;
    case 'decremented':
      return state - 1;
    default:
      return state;
  }
}

const store = createStore(counterReducer, applyMiddleware(thunk, logger));

export default store;

效果

上述介绍了redux中如何使用中间件,接下来我们看看他的执行流程。

三、执行流程和源码

执行流程

Middleware form a pipeline around the store's dispatch method.

redux中使用中间件的流程如图所示,有些同学可能已经开始熟悉了,这不是洋葱模型吗?确实是的,在koa中,洋葱模型正是如此,且也有中间件概念。

实际上,中间件会进行一层一层修饰包裹我们的store,当然其中最主要的还是我们的dispatch

这里我们提一下compose,我们使用componse来实现中间件的执行流程。

function compose(...funcs: Array<Function>): Function {
  if(funcs.length === 0) {
    return (...args: any[]) => args;
  } 
  if(funcs.length === 1) {
    return funcs[0];
  }
  return funcs.reduce((a, b) => (...args: any[]) => a(b(...args)));
}

Redux支持中间件的源码

源码地址:github.com/reduxjs/red…

createStore

首先,我们先看src/createStore代码。

export default function createStore<
  S,
  A extends Action,
  Ext = {},
  StateExt = never
>(
  reducer: Reducer<S, A>,
  preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
  enhancer?: StoreEnhancer<Ext, StateExt>
): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext {
  if (
    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
    (typeof enhancer === 'function' && typeof arguments[3] === 'function')
  ) {
    throw new Error(
      'It looks like you are passing several store enhancers to ' +
        'createStore(). This is not supported. Instead, compose them ' +
        'together to a single function. See https://redux.js.org/tutorials/fundamentals/part-4-store#creating-a-store-with-enhancers for an example.'
    )
  }

  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<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
  }
  // ...code
  dispatch({ type: ActionTypes.INIT } as A)

  const store = {
    dispatch: dispatch as Dispatch<A>,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  } as unknown as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
  return store
}

这里主要是通过接收enhancer参数,从而加入中间件的功能。当有enhancer函数的时候,会将createStore传入生成一个新的函数,用于接收reducerpreloadedState。实际上就是使用了enhancerstore进行包装。

我们通过阅读源码,以及相应的类型,可以发现enhancer返回的类型

源码中类型

export type StoreEnhancer<Ext = {}, StateExt = never> = (
  next: StoreEnhancerStoreCreator<Ext, StateExt>
) => StoreEnhancerStoreCreator<Ext, StateExt>
export type StoreEnhancerStoreCreator<Ext = {}, StateExt = never> = <
  S = any,
  A extends Action = AnyAction
>(
  reducer: Reducer<S, A>,
  preloadedState?: PreloadedState<S>
) => Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext

简单的类型表达如下

type Enhancer = 
    (next: StoreEnhancerStoreCreator) => 
        (reducer: Reducer, preloadedState?: PreloadedState) => 
            Store 

redux中,为我们提供了applyMiddleware函数,让我们只需要编写对应的Middleware,便可以生成Enhancer

applyMiddleware

export default function applyMiddleware<Ext, S = any>(
  ...middlewares: Middleware<any, S, any>[]
): StoreEnhancer<{ dispatch: Ext }>

export default function applyMiddleware(
  ...middlewares: Middleware[]
): StoreEnhancer<any> {
  return (createStore: StoreEnhancerStoreCreator) =>
    <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
      }
    }
}

实际上,applyMiddleware只是通过不断包装dispatch函数,最终返回一个新的store

1. 最基本的store

我们使用了createStore打造了一个最为基础的store对象,此时并没有任何中间件的修饰,所以这时候的storeapi(如dispatch)是最基础的。

export default function applyMiddleware(
  ...middlewares: Middleware[]
): StoreEnhancer<any> {
  return (createStore: StoreEnhancerStoreCreator) =>
    <S, A extends AnyAction>(
      reducer: Reducer<S, A>,
      preloadedState?: PreloadedState<S>
    ) => {
      const store = createStore(reducer, preloadedState)
      // ... some code

      return {
        ...store,
        dispatch
      }
    }
}

2. 提供storeApi,给中间件注入能力

前面提到,redux中间件的是有格式规范的,如下

const loggerMiddleware = storeAPI => next => action => {
  console.log('dispatching', action)
  let result = next(action)
  console.log('next state', storeAPI.getState())
  return result
}

所以,其实我们需要提供的storeApi就是一个store.

注意,这里的middlewareApidispatch是为了保证上层的dispatch的引用不丢失。

const middlewareAPI: MiddlewareAPI = {
  getState: store.getState,
  dispatch: (action, ...args) => dispatch(action, ...args)
}

3. 构建middlewares的chain,使用compose函数实现中间件的执行流程。

const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)

首先,我们看chain是啥

const chain = middlewares.map(middleware => middleware(middlewareAPI))

实际上是一个又一个的数组

[(next)=> action => {...}, (next)=> action => {...}, (next)=> action => {...}, ....]

接着,我们看compose做了啥, 我们先看compose(...chain),相当于前一个中间件的next, 会是后面一个中间件的action函数。但是注意,compose返回的是函数,所以,我们需要将默认的dispatch作为参数传入,就有了。从而实现中间件的执行顺序。

dispatch = compose<typeof dispatch>(...chain)(store.dispatch)

四、 applyMiddlewares的实现

核心任务只要是实现函数序列执行(实质上是对dispatch函数的包装)。

思路

核心在于middlewareChain的实现,使用compose让上一个中间件执行后的结构给到下一个中间价。以下的代码实现了函数序列执行的逻辑。

export function applyMiddleware<S extends any>(...middleWares: Middleware<any>[]) {
  return (createStore: typeof CreateStore) => (reducer: Reducer<S, AnyAction>): Store<S, AnyAction> => {
    const store = createStore<S, AnyAction>(reducer);
    let dispatch = store.dispatch;

    const midApi: Store<S, AnyAction> = {
      ...store,
      dispatch: (action: AnyAction, ...args: any[]) => dispatch(action, ...args)
    };
    const middlewareChain = middleWares.map(middleware => middleware(midApi));

    dispatch = compose(...middlewareChain)(store.dispatch);
    
    return {
      ...store,
      dispatch    };
  }
}

整体代码实现

import { Reducer, Store, Dispatch, Action, AnyAction, Enhancer } from './typings';

export function createStore<S, A extends AnyAction>(reducer: Reducer<S, A>, enhancer?: Enhancer): Store<S, A> {
  if(enhancer) {
    return enhancer(createStore as any)(reducer);
  }

  let currentState: S | undefined;
  let currentListeners: Array<() => void> = [];

  const getState = () => currentState;

  const subscribe: Store<S, A>['subscribe'] = (fn) => {
    currentListeners.push(fn);
    return () => {
      const index = currentListeners.findIndex(fn);
      currentListeners.splice(index, 1);
    }
  }

  const dispatch: Dispatch<A> = (action) => {
    currentState = reducer(currentState, action);
    currentListeners.forEach(listener => listener());
  }

  dispatch({ type: 'SystemInit' } as A);

  return {
    getState,
    subscribe,
    dispatch,
  }
}
import { Store, Middleware, Reducer } from './typings';
import { AnyAction } from 'redux';
import { createStore as CreateStore } from './index';

export function applyMiddleware<S extends any>(...middleWares: Middleware<S>[]) {
  return (createStore: typeof CreateStore) => (reducer: Reducer<S, AnyAction>): Store<S, AnyAction> => {
    const store = createStore<S, AnyAction>(reducer);
    let dispatch = store.dispatch;

    const midApi: Store<S, AnyAction> = {
      ...store,
      dispatch: (action: AnyAction, ...args: any[]) => dispatch(action, ...args)
    };
    const middlewareChain = middleWares.map(middleware => middleware(midApi));

    dispatch = compose(...middlewareChain)(store.dispatch);
    
    return {
      ...store,
      dispatch    };
  }
}

function compose(...funcs: Array<Function>): Function {
  if(funcs.length === 0) {
    return (...args: any[]) => args;
  } 
  if(funcs.length === 1) {
    return funcs[0];
  }
  return funcs.reduce((a, b) => (...args: any[]) => a(b(...args)));
}

效果

我们使用两个中间件试一下。由于并没有使用redux中的类型定义,导致applyMiddleware使用了两个第三方的middleware类型会不同,所以这里先用any进行hack处理。

import { Action } from './../lib/redux-nut/typings';

// import { createStore, Action, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import logger from "redux-logger";

import { createStore } from '../lib/redux-nut';
import { applyMiddleware } from '../lib/redux-nut/applyMiddleware';

function counterReducer(state = 0, action: Action<'incremented' | 'decremented'>) {
  switch (action.type) {
    case 'incremented':
      return state + 1;
    case 'decremented':
      return state - 1;
    default:
      return state;
  }
}

const store = createStore(counterReducer, applyMiddleware(thunk as any, logger as any));

export default store;

参考资料