记录下 redux 源码阅读

427 阅读2分钟

写在前面

记录下我看 redux 源码的笔记,其中关于 replace, observable 的部分我没有去细看,因为从没用过...

参考资料

juejin.cn/post/684490…

目录

  • applyMiddleware
  • bindActionCreators
  • combineReducers
  • compose
  • createStore
  • index.js
  • utils

index.js

先从入口开始。

// 导出了我们熟悉的 api
import createStore from "./createStore";
import combineReducers from "./combineReducers";
import bindActionCreators from "./bindActionCreators";
import applyMiddleware from "./applyMiddleware";
import compose from "./compose";
// 控制台打印的函数
import warning from "./utils/warning";
// replace, init, unknown(也就是 action type 是随机的类型)三种类型
import __DO_NOT_USE__ActionTypes from "./utils/actionTypes";

/*
 * 通过这个函数名字是否变化检测是不是压缩了代码
 */

function isCrushed() {}

if (
  process.env.NODE_ENV !== "production" &&
  typeof isCrushed.name === "string" &&
  isCrushed.name !== "isCrushed"
) {
  warning(
    'You are currently using minified code outside of NODE_ENV === "production". ' +
      "This means that you are running a slower development build of Redux. " +
      "You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify " +
      "or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) " +
      "to ensure you have the correct code for your production build."
  );
}

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes,
};

createStore

import $$observable from "symbol-observable";
import ActionTypes from "./utils/actionTypes";
import isPlainObject from "./utils/isPlainObject";

/**
 *
 */
export default function createStore(reducer, preloadedState, enhancer) {
  // 不能传多个 enhancer
  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."
    );
  }

  // 第二个参数 preloadedState 的位置是函数,enhancer 没传则认为第二个参数传的是 enhancer
  if (typeof preloadedState === "function" && typeof enhancer === "undefined") {
    enhancer = preloadedState;
    preloadedState = undefined;
  }

  // enhancer 必须是个函数
  if (typeof enhancer !== "undefined") {
    if (typeof enhancer !== "function") {
      throw new Error("Expected the enhancer to be a function.");
    }

    /**
     * 创建 store 的时候 createStore( reducer, compose(applyMiddleware(thunk)) )
     * compose 返回一个函数作为这里的 enchancer,而这个函数可以在 applyMiddleware 源码里看到以 createStore 为参数
     */
    return enhancer(createStore)(reducer, preloadedState);
  }

  // reduer 必须是函数
  if (typeof reducer !== "function") {
    throw new Error("Expected the reducer to be a function.");
  }

  let currentReducer = reducer;
  let currentState = preloadedState;
  let currentListeners = [];
  let nextListeners = currentListeners;
  let isDispatching = false;

  /**
   * 这个函数会在 subscribe & unsubscribe 中调用
   * 在 dispatch 中,将 nextListener 赋值给了 currentListener,然后依次执行了每个函数
   * 如果函数调用执行过程中新订阅或取消了 listner 且只有一个数组保存所有订阅,那么会导致 listener 数组长度的不确定性,因此
   * 长度的操作都发生在 nextListener 而遍历时针对的是它的快照 currentListener
   */
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice();
    }
  }

  /**
   * 返回现在的状态
   */
  function getState() {
    if (isDispatching) {
      throw new Error(
        "You may not call store.getState() while the reducer is executing. " +
          "The reducer has already received the state as an argument. " +
          "Pass it down from the top reducer instead of reading it from the store."
      );
    }

    return currentState;
  }

  /**
   * 不能再 reducer 里订阅或取消
   */
  function subscribe(listener) {
    if (typeof listener !== "function") {
      throw new Error("Expected the listener to be a function.");
    }

    if (isDispatching) {
      throw new Error(
        "You may not call store.subscribe() while the reducer is executing. " +
          "If you would like to be notified after the store has been updated, subscribe from a " +
          "component and invoke store.getState() in the callback to access the latest state. " +
          "See https://redux.js.org/api-reference/store#subscribelistener for more details."
      );
    }

    let isSubscribed = true;

    ensureCanMutateNextListeners();
    nextListeners.push(listener);

    return function unsubscribe() {
      if (!isSubscribed) {
        return;
      }

      if (isDispatching) {
        throw new Error(
          "You may not unsubscribe from a store listener while the reducer is executing. " +
            "See https://redux.js.org/api-reference/store#subscribelistener for more details."
        );
      }

      isSubscribed = false;

      ensureCanMutateNextListeners();
      const index = nextListeners.indexOf(listener);
      nextListeners.splice(index, 1);
      currentListeners = null;
    };
  }

  /**
   * action 必须是纯对象(原型链仅存一个父类,比如单纯的字面量对象)
   * reducer 不能 dispatch
   */
  function dispatch(action) {
    if (!isPlainObject(action)) {
      throw new Error(
        "Actions must be plain objects. " +
          "Use custom middleware for async actions."
      );
    }

    if (typeof action.type === "undefined") {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
          "Have you misspelled a constant?"
      );
    }

    if (isDispatching) {
      throw new Error("Reducers may not dispatch actions.");
    }

    try {
      isDispatching = true;
      currentState = currentReducer(currentState, action);
    } finally {
      isDispatching = false;
    }

    // 执行 Listeners
    const listeners = (currentListeners = nextListeners);
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i];
      listener();
    }

    // 返回 action 是 middleware 嵌套调用 dispatch 的关键
    return action;
  }

  /**
   * 我没用过这个,暂时放着(*^▽^*),看英文作用是代码分割,动态加载 reducer???
   * Replaces the reducer currently used by the store to calculate the state.
   *
   * You might need this if your app implements code splitting and you want to
   * load some of the reducers dynamically. You might also need this if you
   * implement a hot reloading mechanism for Redux.
   *
   * @param {Function} nextReducer The reducer for the store to use instead.
   * @returns {void}
   */
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== "function") {
      throw new Error("Expected the nextReducer to be a function.");
    }

    currentReducer = nextReducer;

    // This action has a similiar effect to ActionTypes.INIT.
    // Any reducers that existed in both the new and old rootReducer
    // will receive the previous state. This effectively populates
    // the new state tree with any relevant data from the old one.
    dispatch({ type: ActionTypes.REPLACE });
  }

  /**我没用过这个,暂时放着(*^▽^*)
   * Interoperability point for observable/reactive libraries.
   * @returns {observable} A minimal observable of state changes.
   * For more information, see the observable proposal:
   * https://github.com/tc39/proposal-observable
   */
  function observable() {
    const outerSubscribe = subscribe;
    return {
      /**
       * The minimal observable subscription method.
       * @param {Object} observer Any object that can be used as an observer.
       * The observer object should have a `next` method.
       * @returns {subscription} An object with an `unsubscribe` method that can
       * be used to unsubscribe the observable from the store, and prevent further
       * emission of values from the observable.
       */
      subscribe(observer) {
        if (typeof observer !== "object" || observer === null) {
          throw new TypeError("Expected the observer to be an object.");
        }

        function observeState() {
          if (observer.next) {
            observer.next(getState());
          }
        }

        observeState();
        const unsubscribe = outerSubscribe(observeState);
        return { unsubscribe };
      },

      [$$observable]() {
        return this;
      },
    };
  }

  // 初始化 store state
  dispatch({ type: ActionTypes.INIT });

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable,
  };
}

applyMiddleware

export default function applyMiddleware(...middlewares) {
  // 返回一个接收 createStore 作为参数的函数
  // 可看下 createStore.js 中 return enhancer(createStore)(reducer, preloadedState);
  // 可以看到再执行该函数后,返回了覆盖掉 store 对象 dispatch 方法的对象,因此该函数关键是对于 dispatch 的改造
  return (createStore) => (...args) => {
    // 首先根据传入的 (reducer, preloadedState) 初始化 store 对象
    const store = createStore(...args);
    // 先将 dispatch 初始化为一个执行后会报错的函数
    // 保证中间件在初始化过程中不能 dispatch
    let dispatch = () => {
      throw new Error(
        "Dispatching while constructing your middleware is not allowed. " +
          "Other middleware would not be applied to this dispatch."
      );
    };

    // 这里 dispatch 进行了一个延迟绑定,下边 compose... 重新赋值了 dispatch
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args),
    };
    // 额... 这里我看了下 redux-thunk 的源码,可简化为酱紫
    // (getState, dispatch) => next => action => dispatch(action) / action()
    // next 参数是前一个中间件的 (action) => dispatch(action) / action(),当然对于第一个中间件来说就是未改造的 store.dispatch
    const chain = middlewares.map((middleware) => middleware(middlewareAPI));
    // compose 把所有的 middleware 函数进行了嵌套调用,比如有 a, b, c, d
    // 那么 最终结果是 () => d(c(b(a())))
    // 这里有点难,实际上 applyMiddleware 最后改造的 dispatch 是 (action) => f(c(b(a(action)))),可以看到每次返回 action 的重要性
    dispatch = compose(...chain)(store.dispatch);

    return {
      ...store,
      dispatch,
    };
  };
}

compose

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return (arg) => arg;
  }

  if (funcs.length === 1) {
    return funcs[0];
  }

  // 在 applyMiddleware 中,dispatch = compose(...chain)(store.dispatch) 接收了初始化的中间件数组
  // 如果只有两个中间件 a, b;那么返回的结果是 (...args) => a(b(...args))
  // 这里 funcs.reduce 的作用是把一堆中间件的函数通过这个方法组合成一个嵌套调用的新函数 (...args) => c(b(a(...args)))
  return funcs.reduce((a, b) => (...args) => a(b(...args)));
}

写在最后

主要部分就这些