Redux源码解析(2)

1,257 阅读4分钟

关注不迷路→# Redux源码解析(1)

安装使用

这一篇我们来聊聊redux的中间件,先贴一个最基本的使用方法

import React, { useEffect, useRef } from "react";
import "./App.css";
import { type Store, createStore, applyMiddleware } from "redux-code";

interface Iaction {
  type: string;
}

function App() {
  const storeRef: React.MutableRefObject<Store | null> = useRef(null);

  useEffect(() => {
    const logger = (store: any) => (next: any) => (action: any) => {
      console.log("1111");
      console.log(next);

      let result = next(action);
      console.log("22222");
      return result;
    };

    const logger2 = (store: any) => (next: any) => (action: any) => {
      console.log("3333");
      let result = next(action);
      console.log("44444");
      return result;
    };

    const logger3 = (store: any) => (next: any) => (action: any) => {
      console.log("5555");
      let result = next(action);
      console.log("66666");
      return result;
    };

    const counterReducer = (state = { value: 0 }, action: Iaction) => {
      switch (action.type) {
        case "counter/incremented":
          return { value: state.value + 1 };
        case "counter/decremented":
          return { value: state.value - 1 };
        default:
          return state;
      }
    };
    storeRef.current = createStore(
      counterReducer,
      applyMiddleware(logger, logger2, logger3)
    );

    storeRef.current.subscribe(() => {
      console.log(storeRef.current?.getState());
    });
  }, []);

  return (
    <div className="App">
      <button
        onClick={() =>
          storeRef.current?.dispatch({ type: "counter/incremented" })
        }
      >
        ADD
      </button>
    </div>
  );
}

export default App;

image.png

代码与上一篇文章的基本用法基本差不多,只不过是在createStore方法入参多了applyMiddleware函数包裹的value。在执行onClick方法的时候会调用logger1,logger2,logger3里面的方法。

什么是Redux的中间件

根据我们能查到的资料给中间件下个定义

中间件就是一系列的处理逻辑,对于某个特定场景下的对象实体,程序会依次调用该系列处理逻辑进行处理。

image.png

通俗点理解中间件就是在函数的执行过程中预设可插入的、并不会影响执行结果一或多段代码块(函数)。 在redux执行中间件的流程中,“插入”这个动作就是createStore函数传入第三个参数applyMiddleware(logger, logger2, logger3),对应createStore函数形参就是enhancer;“一或多段代码块(函数)”的组合对应的就是redux的compose api。 基于上述概念和上文总结出来的流程我们可以大致推断出在传入中间件情况下createStore的执行流程:

  1. 检测到有中间件函数传入,组合拼接中间件函数,拦截dispatch进行二次包装
  2. 声明一个结合state和action的reducer函数,传入createStore函数生成一个Store类型对象,步骤1生成的新dispatch方法替换Store类型对象的dispatch
  3. 通过Store类型对象的subscribe方法提供的回调函数进行事件监听
  4. Store类型对象dispatch,依次执行中间件函数,监听事件生效

源码解析

根据App.tsx我们在createStore函数中的第三个参数中传入了applyMiddleware(logger, logger2, logger3),进入createStore.ts函数中,enhancer有值,符合代码段中

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
  }

的条件,进入applyMiddleware.ts查看api

/**
 * 三重函数,依次需要传入中间件列表、创建store方法和state/reducer
 */
export default function applyMiddleware(
  ...middlewares: Middleware[]
): StoreEnhancer<any> {
  return createStore =>
    <S, A extends AnyAction>(
      reducer: Reducer<S, A>,
      preloadedState?: PreloadedState<S>
    ) => {
      // 常规创建Store对象
      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.'
        )
      }
      // 以下为dispatch覆盖
      const middlewareAPI: MiddlewareAPI = {
        getState: store.getState,
        dispatch: (action, ...args) => dispatch(action, ...args)
      }
      /**
       *middlewares demo:
       *三重函数
       *const logger = (store: any) => (next: any) => (action: any) => {
       *  console.log("1111");
       *  console.log(next);

       *  let result = next(action);
       *  console.log("22222");
       *  return result;
       *};
       */
      // 解第一重,传入类似Store类型的对象
      const chain = middlewares.map(middleware => middleware(middlewareAPI))
      // 串联中间件函数组合生成新dispatch
      dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
      // 覆盖
      return {
        ...store,
        dispatch
      }
    }
}

进入compose.ts

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: any) =>
        a(b(...args))
  )
}

最后一句话无疑是最核心的关键点

让我们对此进行单独测试,假设compose函数参数是[a1,b1,c1,d1]四个函数类型组成的数组:

  1. 第一次循环,生成return的结果是(...args) =>a1(b1(...args))
  2. 第二次循环形参a,b接受的值为(...args) =>a1(b1(...args))和c1,生成(...args2) => ((...args) =>a1(b1(...args)))(c1(...args2)),其中c1(...args2)就是(...args) =>a1(b1(...args))的入参,代入执行就变成(...args2) => a1(b1(c1(...args2))),简化入参名变为(...args) => a1(b1(c1(...args)))
  3. 第三次循环a,b接受的值为(...args) => a1(b1(c1(...args)))和d1,生成(...args2) => ((...args) => a1(b1(c1(...args))))(d1(...args2)),其中d1(...args2)(...args) => a1(b1(c1(...args)))的入参,代入执行变为(...args2) => a1(b1(c1(d1(...args2)))),简化入参名变为(...args) => a1(b1(c1(d1(...args))))

总结上述流程,最关键的一点是第二个函数执行新生成函数的形参作为第一个函数的入参进行执行从而简化掉第一个函数的入参

回到代码,

/**
    chain里面的元素经过一层执行后可以变成如下形式
    [
        (next) => {
            return (action) => {
                // ...
                let result = next(action);
                // ...
                retun result
            }
        },
        (next) => {
            return (action) => {
                // ...
                let result = next(action);
                // ...
                retun result
            }
        },
        (next) => {
            return (action) => {
                // ...
                let result = next(action);
                // ...
                retun result
            }
        }
    ]
*/
const chain = middlewares.map(middleware => middleware(middlewareAPI))
/**
    第一次执行:
    数组列表的函数变成(...args) => a(b(c(...args)))的形式
    第二次执行:
    第一个函数入参next就是第二个函数,而第二个函数入参next就是第三个函数,第三个函数的入参next就是最外头传进来的store.dispatch,return出来的就是(action) => store.dispatch(action)
*/
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)

最后执行结果类似为这样

dispatch = (action) => {
    // 过程
    return dispatch(action)
    // 过程
}

符合createStore中dispatch的类型(action) => action

最后执行顺序如下 image.png