Javascript

React-Redux原理简析与源码解析

本文主要是介绍React-Redux原理简析与源码解析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

没有看过Redux源码的同学可以看看我的上一篇文章Redux原理简析与源码解析

本文发布在我的博客

本文的github仓库

自从我学习过高阶组件,大家流传的就是connect()就是一个高阶组件,所以在我长久的认知中,认为connect()仅仅是一个从context中筛选出state,传入给子组件的简单高阶组件

我个人用redux是比较少的,自从hooks出了以后,就经常用useContext+useReducer来实现简单的redux的功能,所以也认为在react-redux里,connect()会像useReducer一样,数据重新渲染React控制

结论当然不是这样,并且connect()组件也不简单,可以说在我打开源码的第一时间傻眼了,下面就来好好的分析下react-redux

原理简述

在分析源码之前,需要先将react-redux的原理简述一下,否则光干看代码还是会比较懵

先简单的画一个流程

react-redux的核心机制是通知订阅模式,源码中有一个Subscription类,它的作用主要是订阅父级的更新和通知子级的更新,也就是它既可以订阅别人,别人也可以订阅它,同时可以通知订阅它的Subscription

最外层的Provider组件的Context里包含了的store(也就是我们传入的)和生成的Subscription实例,它的Subscription实例订阅的则是reduxsubscrib()

当我们使用了connect()时,它会生成一个新组件<Component1/><Component1/>里会生成一个Subscription实例,它会订阅父级(这时是Provider)的Subscription实例,同时将自己的Subscription覆盖进Context,再包装我们传入的组件,如下模式

// overriddenContextValue包含了新组件的Subscription实例和store
<Component1.Provider value={overriddenContextValue}>
  {WrappedComponent}
</Component1.Provider>
复制代码

如果在<Component1/>里的子组件又有connect(),那么生成的<Component2/>组件的Subscription实例会订阅父级<Component1/>Subscription实例,同时再将自己的Subscription覆盖进Context

在组件挂载完成后,如果store有更新,Provider会通知下一级组件的Subscription,下一级组件又会通知自己的下一级组件

<Provider store={store}>
  <Component1> // 它订阅的Provider
  	<Component2/> // 它订阅的Component1
  <Component1/>
</Provider>
// 当store有更新,Provider通知Component1,Component1通知Component2
复制代码

在订阅的时候,会将更新自己组件的方法通过回调onStateChange()传入父级的Subscription

一旦父级接收到通知,就会循环调用订阅自己的组件的onStateChange来更新它们

更新的原理就是使用我们传入的mapStateToPropsmapDispatchToProps,结合内置的selectorFactor()来对比stateprops,一旦有改变就强制更新自己,所以我们传入的WrappedComponent也被强制更新了

原理简单来讲就是这样,下面来看源码

源码简析

在顺着流程分析之前,先看看贯通整个react-redux更新流程的Subscription

Subscription

// Subscriotion.js
const nullListeners = { notify() {} };

// 监听集合是一个双向链表
function createListenerCollection() {
  // 也就是React里的unstable_batchedUpdates
  // 来自司徒正美微博:unstable_batchedUpdates会把子组件的forceUpdate干掉,防止组件在一个批量更新中重新渲染两次
  const batch = getBatch();
  let first = null;
  let last = null;

  return {
    clear() {
      first = null;
      last = null;
    },

    // 通知订阅者更新
    notify() {
      batch(() => {
        let listener = first;
        while (listener) {
          // 这个callback的本质就是让组件本身forceUpdate
          listener.callback();
          listener = listener.next;
        }
      });
    },

    // 订阅
    subscribe(callback) {
      let isSubscribed = true;
      // 把last赋值为新的
      let listener = (last = {
        callback,
        next: null,
        prev: last
      });

      // 如果存在前一个,就把前一个的next指向当前(最后一个)
      if (listener.prev) {
        listener.prev.next = listener;
      } else {
        // 否则它就是第一个
        first = listener;
      }

      // 返回退订函数
      return function unsubscribe() {
      	// ...退订逻辑
      };
    }
  };
}

export default class Subscription {
  constructor(store, parentSub) {
    // redux store
    this.store = store;
    // 父级的Subscription实例
    this.parentSub = parentSub;
    // 退订函数
    this.unsubscribe = null;
    // 监听者
    this.listeners = nullListeners;

    this.handleChangeWrapper = this.handleChangeWrapper.bind(this);
  }

  // 添加嵌套的订阅者
  addNestedSub(listener) {
    // 首先先将当前的Subscription实例绑定到父级
    // 绑定的同时会初始化listeners
    this.trySubscribe();
    return this.listeners.subscribe(listener);
  }

  // 通知子级
  notifyNestedSubs() {
    this.listeners.notify();
  }

  // 当父级Subscription的listeners通知时调用
  handleChangeWrapper() {
    // 这个是new出实例的时候加上的,感觉有点秀
    if (this.onStateChange) {
      this.onStateChange();
    }
  }

  trySubscribe() {
    // 不会重复绑定
    if (!this.unsubscribe) {
      this.unsubscribe = this.parentSub
        ? this.parentSub.addNestedSub(this.handleChangeWrapper)
        : // subscribe是redux里的方法,在redux state改变的时候会调用
          this.store.subscribe(this.handleChangeWrapper);
      // 创建新的listeners,每个connect的组件都会有listeners
      this.listeners = createListenerCollection();
    }
  }

  // 退订
  tryUnsubscribe() {
    if (this.unsubscribe) {
      this.unsubscribe();
      this.unsubscribe = null;
      this.listeners.clear();
      this.listeners = nullListeners;
    }
  }
}
复制代码

省略了一些代码,Subscription类主要就是创建一个既有监听功能又有订阅功能的对象

接下来就顺着流程来逐步分析,首先先看Provider里实现了什么

Provider

// components/Provider.js

function Provider({ store, context, children }) {
  // useMemo仅在store变化时再重新返回
  const contextValue = useMemo(() => {
    const subscription = new Subscription(store);
    // 通知订阅这个subscription的子级刷新
    subscription.onStateChange = subscription.notifyNestedSubs;
    return {
      store,
      // 将此subscription传入context方便子级订阅
      subscription
    };
  }, [store]);

  // 缓存上次的state
  const previousState = useMemo(() => store.getState(), [store]);

  useEffect(() => {
    const { subscription } = contextValue;
    // 在这里是订阅的reudx store的subscribe事件
    subscription.trySubscribe();
    if (previousState !== store.getState()) {
      subscription.notifyNestedSubs();
    }
    return () => {
      subscription.tryUnsubscribe();
      subscription.onStateChange = null;
    };
  }, [contextValue, previousState, store]);

  // 传入的context或者react-redux自带的
  const Context = context || ReactReduxContext;

  return <Context.Provider value={contextValue}>{children}</Context.Provider>;
}
复制代码

Provider是一个比较简单的组件,主要做了2件事

  • 订阅reduxsubscribe()事件
  • Subscription实例传入Context方便子级订阅

接下来看看核心connect组件

connect

// connect.js

// 遍历执行函数,将arg作为参数传入,如果有结果则return
function match(arg, factories, name) {
  for (let i = factories.length - 1; i >= 0; i--) {
    const result = factories[i](arg);
    if (result) return result;
  }
  &emsp;// ... error
}

// ...

export function createConnect({
  // 默认值
  connectHOC = connectAdvanced,
  mapStateToPropsFactories = defaultMapStateToPropsFactories,
  mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
  mergePropsFactories = defaultMergePropsFactories,
  selectorFactory = defaultSelectorFactory
} = {}) {
  return function connect(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    {
      pure = true,
    	// ...省略一些参数
    } = {}
  ) {
    const initMapStateToProps = match(
      mapStateToProps,
      mapStateToPropsFactories,
      "mapStateToProps"
    );
    const initMapDispatchToProps = match(
      mapDispatchToProps,
      mapDispatchToPropsFactories,
      "mapDispatchToProps"
    );
    const initMergeProps = match(mergeProps, mergePropsFactories, "mergeProps");
    return connectHOC(selectorFactory, {
      initMapStateToProps,
      initMapDispatchToProps,
      initMergeProps,
      // ...省略了一些options
    });
  };
}

export default /*#__PURE__*/ createConnect();
复制代码

connect()其实是由createConnect()默认创建出来的,虽然我们也可以调用createConnect()创建自定义的connect(),但是基本用不上

可以看到我们传入的mapStateToPropsmapDispatchToPropsmergeProps实际上是通过了一个match()函数的包装校验

这里就以mapStateToPropsFactories也就是defaultMapStateToPropsFactories为例

mapStateToPropsFactories

// mapStateToProps.js

import { wrapMapToPropsConstant, wrapMapToPropsFunc } from "./wrapMapToProps";

export function whenMapStateToPropsIsFunction(mapStateToProps) {
  return typeof mapStateToProps === "function"
    ? wrapMapToPropsFunc(mapStateToProps, "mapStateToProps")
    : undefined;
}

export function whenMapStateToPropsIsMissing(mapStateToProps) {
  return !mapStateToProps ? wrapMapToPropsConstant(() => ({})) : undefined;
}

export default [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing];
复制代码

match校验的时候,首先会判断我们是否传入的mapStateToProps,没有传入则调用wrapMapToPropsConstant创建一个默认方法

如果传入则会调用wrapMapToPropsFunc对我们的方法做一层包装,主要判断我们的方法是否需要依赖props

wrapMapToProps
// wrapMapToProps.js

// ...

// 
export function wrapMapToPropsFunc(mapToProps, methodName) {
  return function initProxySelector(dispatch, { displayName }) {
    const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
      return proxy.dependsOnOwnProps
        ? proxy.mapToProps(stateOrDispatch, ownProps)
        : proxy.mapToProps(stateOrDispatch);
    };
    // 根据dependsOnOwnProps的值来判断是否需要在props改变时重新调用
    // 默认为true,因为要使用detectFactoryAndVerify
    proxy.dependsOnOwnProps = true;

    proxy.mapToProps = function detectFactoryAndVerify(
      stateOrDispatch,
      ownProps
    ) {
      // detectFactoryAndVerify方法只会调用一次
      // 第一次调用后就会被我们传入的mapToProps覆盖掉
      proxy.mapToProps = mapToProps;
      // 这里会判断函数是否依赖于props
      // getDependsOnOwnProps()的主要逻辑就是判断函数的参数个数,如果依赖props则参数等于2,返回true
      proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps);
      // 这时的值是由我们传入的mapToProps返回的
      let props = proxy(stateOrDispatch, ownProps);
      // 如果props是一个函数的情况在官方文档有讲,不过感觉这应该是高阶用法了,小张没有用过
      // https://react-redux.js.org/api/connect#factory-functions
      if (typeof props === "function") {
        proxy.mapToProps = props;
        proxy.dependsOnOwnProps = getDependsOnOwnProps(props);
        props = proxy(stateOrDispatch, ownProps);
      }
      return props;
    };
    return proxy;
  };
}

复制代码

这里的判断实际都是为了给后面的selectorFactory铺路,它的作用是根据stateprops的变化,判断是否需要调用我们的mapStateToPropsmapDispatchToPropsmergeProps返回新的数据

下面看看selectorFactory的实现

selectorFactory

// selectorFactory.js

// 如果pure为false,则每次都会调用我们都mapStateToProps方法获得新的数据
export function impureFinalPropsSelectorFactory(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
  dispatch
) {
  return function impureFinalPropsSelector(state, ownProps) {
    return mergeProps(
      mapStateToProps(state, ownProps),
      mapDispatchToProps(dispatch, ownProps),
      ownProps
    );
  };
}

// pure为true时会判断值是否相同,不相同才调用,pure默认为true
export function pureFinalPropsSelectorFactory(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
  dispatch,
  { areStatesEqual, areOwnPropsEqual, areStatePropsEqual }
) {
  // 至少运行过一次
  let hasRunAtLeastOnce = false;
  // 保存下来做对比
  let state;
  let ownProps;
  let stateProps;
  let dispatchProps;
  let mergedProps;

  // 第一次调用Selector初始化把值都存下来,方便后面的比较
  function handleFirstCall(firstState, firstOwnProps) {
    state = firstState;
    ownProps = firstOwnProps;
    stateProps = mapStateToProps(state, ownProps);
    dispatchProps = mapDispatchToProps(dispatch, ownProps);
    mergedProps = mergeProps(stateProps, dispatchProps, ownProps);
    hasRunAtLeastOnce = true;
    // 返回的都是mergedProps
    return mergedProps;
  }

  function handleNewPropsAndNewState() {
    // 重新计算redux store产生的props
    stateProps = mapStateToProps(state, ownProps);

    // 如果mapDispatchToProps需要根据props来改变,就需要重新计算
    if (mapDispatchToProps.dependsOnOwnProps)
      dispatchProps = mapDispatchToProps(dispatch, ownProps);

    // 将redux props和dispatch props和传入组件的props合并
    mergedProps = mergeProps(stateProps, dispatchProps, ownProps);
    return mergedProps;
  }

  function handleNewProps() {
    // 如果mapStateToProps需要获取组件的props,就需要重新计算
    if (mapStateToProps.dependsOnOwnProps)
      stateProps = mapStateToProps(state, ownProps);
    // 如果mapDispatchToProps需要获取组件的props,就需要重新计算
    if (mapDispatchToProps.dependsOnOwnProps)
      dispatchProps = mapDispatchToProps(dispatch, ownProps);
    // 合并返回
    mergedProps = mergeProps(stateProps, dispatchProps, ownProps);
    return mergedProps;
  }

  function handleNewState() {
    const nextStateProps = mapStateToProps(state, ownProps);
    const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps);
    stateProps = nextStateProps;
    // 只有改变了才重新merge
    if (statePropsChanged)
      mergedProps = mergeProps(stateProps, dispatchProps, ownProps);

    return mergedProps;
  }

  function handleSubsequentCalls(nextState, nextOwnProps) {
    const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps);
    const stateChanged = !areStatesEqual(nextState, state);
    state = nextState;
    ownProps = nextOwnProps;

    // 如果props和state都改变了
    if (propsChanged && stateChanged) return handleNewPropsAndNewState();
    if (propsChanged) return handleNewProps();
    if (stateChanged) return handleNewState();
    return mergedProps;
  }

  return function pureFinalPropsSelector(nextState, nextOwnProps) {
    return hasRunAtLeastOnce
      ? handleSubsequentCalls(nextState, nextOwnProps)
      : handleFirstCall(nextState, nextOwnProps);
  };
}

// 最终返回的函数
export default function finalPropsSelectorFactory(
  dispatch,
  { initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options }
) {
  // 传入的函数全部经过了wrapMapToProps.js里的wrapMapToPropsFunc重新包装(proxy)
  const mapStateToProps = initMapStateToProps(dispatch, options);
  const mapDispatchToProps = initMapDispatchToProps(dispatch, options);
  const mergeProps = initMergeProps(dispatch, options);
  if (process.env.NODE_ENV !== "production") {
    verifySubselectors(
      mapStateToProps,
      mapDispatchToProps,
      mergeProps,
      options.displayName
    );
  }

  // pure浅对比调用pureFinalPropsSelectorFactory,里面会对比是否需要更新
  const selectorFactory = options.pure
    ? pureFinalPropsSelectorFactory
    : impureFinalPropsSelectorFactory;

  // 如果是pure返回的也就是pureFinalPropsSelectorFactory里的pureFinalPropsSelector函数
  return selectorFactory(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    dispatch,
    options
  );
}

复制代码

现在的流程,知道了调用connect()后,会对我们的传入的函数进行一层包装来判断是否依赖于props,随后selectorFactory调用时会根据结果有无变化来判断是否需要重新调用我们的函数

现在就来看核心的高阶函数的实现connectAdvanced

connectAdvanced

import hoistStatics from "hoist-non-react-statics";
import React, { useContext, useMemo, useRef, useReducer } from "react";
import { isValidElementType, isContextConsumer } from "react-is";
import Subscription from "../utils/Subscription";
import { useIsomorphicLayoutEffect } from "../utils/useIsomorphicLayoutEffect";

import { ReactReduxContext } from "./Context";

// 使用useReducer的初始值
const EMPTY_ARRAY = [];
// 组件不被订阅的值
const NO_SUBSCRIPTION_ARRAY = [null, null];

//useReducer的reducer
function storeStateUpdatesReducer(state, action) {
  const [, updateCount] = state;
  return [action.payload, updateCount + 1];
}

function useIsomorphicLayoutEffectWithArgs(
  effectFunc,
  effectArgs,
  dependencies
) {
  useIsomorphicLayoutEffect(() => effectFunc(...effectArgs), dependencies);
}

function captureWrapperProps(
  lastWrapperProps,
  lastChildProps,
  renderIsScheduled,
  wrapperProps,
  actualChildProps,
  childPropsFromStoreUpdate,
  notifyNestedSubs
) {
  // 存下来用于下次的比较
  lastWrapperProps.current = wrapperProps;
  lastChildProps.current = actualChildProps;
  renderIsScheduled.current = false;
  // 如果更新来自store,则清空引用并且通知子级更新
  if (childPropsFromStoreUpdate.current) {
    childPropsFromStoreUpdate.current = null;
    notifyNestedSubs();
  }
}

function subscribeUpdates(
  // 是否需要更新
  shouldHandleStateChanges,
  store,
  // Subscription的实例
  subscription,
  // connect的selector
  childPropsSelector,
  // 上一次传入组件的props
  lastWrapperProps,
  // 上一次的props包括组件的props,store props,dispatch props
  lastChildProps,
  renderIsScheduled,
  childPropsFromStoreUpdate,
  notifyNestedSubs,
  forceComponentUpdateDispatch
) {
  // 不需要更新
  if (!shouldHandleStateChanges) return;
  let didUnsubscribe = false;
  let lastThrownError = null;
  // 每当store的订阅更新传递到此组件都会运行这个回调
  const checkForUpdates = () => {
    if (didUnsubscribe) {
      // redux不能保证在下次dispatch前取消订阅
      return;
    }
    // 新的state
    const latestStoreState = store.getState();
    let newChildProps, error;
    try {
      // 获取新的child props
      newChildProps = childPropsSelector(
        latestStoreState,
        lastWrapperProps.current
      );
    } catch (e) {
      error = e;
      lastThrownError = e;
    }

    if (!error) {
      lastThrownError = null;
    }
    // 如果child props没有变就什么都不做
    if (newChildProps === lastChildProps.current) {
      // 即便自己没变,也要通知订阅自己的子级去检查更新
      if (!renderIsScheduled.current) {
        notifyNestedSubs();
      }
    } else {
      // 把新的child props存下来,使用ref而不是useState/useReducer是因为我们需要一种方式确定值是否已经被处理
      // 如果用useState/useReducer,我们不能在不强制更新的情况下清除值,这不是我们想要的
      lastChildProps.current = newChildProps;
      childPropsFromStoreUpdate.current = newChildProps;
      renderIsScheduled.current = true;
      // 如果child props改变或者捕获了错误,这个wrapper component都需要重新渲染
      forceComponentUpdateDispatch({
        type: "STORE_UPDATED",
        payload: {
          error,
        },
      });
    }
  };

  // 实际订阅的是最近的父级或者是store
  subscription.onStateChange = checkForUpdates;
  // 订阅
  subscription.trySubscribe();

  checkForUpdates();

  // 退订
  const unsubscribeWrapper = () => {
    didUnsubscribe = true;
    subscription.tryUnsubscribe();
    subscription.onStateChange = null;

    if (lastThrownError) {
      throw lastThrownError;
    }
  };

  return unsubscribeWrapper;
}

// useReducer惰性初始化
const initStateUpdates = () => [null, 0];

export default function connectAdvanced(
  selectorFactory,
  {
    // 这个函数通过wrapped component的displayName来计算HOC的displayName
    // 可能会被wrapper functions例如connect() 覆盖
    getDisplayName = name => `ConnectAdvanced(${name})`,
    // 在error messages里显示
    methodName = "connectAdvanced",
    // REMOVED
    renderCountProp = undefined,
    // false的时候dispatch里组件也不会更新
    shouldHandleStateChanges = true,
    // REMOVED
    storeKey = "store",
    // REMOVED
    withRef = false,
    // 是否传递ref
    forwardRef = false,
    // 使用的context consumer
    context = ReactReduxContext,
    // 其他值将传递给selectorFactory
    ...connectOptions
  } = {}
) {
  // ...

  // context
  const Context = context;
  // 实际connect调用的函数,WrappedComponent就是传入的组件
  return function wrapWithConnect(WrappedComponent) {
    // 传入组件的名字,在react插件上看得到
    const wrappedComponentName =
      WrappedComponent.displayName || WrappedComponent.name || "Component";
    const displayName = getDisplayName(wrappedComponentName);
    // 传递给selectorFactory
    const selectorFactoryOptions = {
      ...connectOptions,
      getDisplayName,
      methodName,
      renderCountProp,
      shouldHandleStateChanges,
      storeKey,
      displayName,
      wrappedComponentName,
      WrappedComponent,
    };
    // 是否缓存值
    const { pure } = connectOptions;
    // 封装一下selectorFactory
    function createChildSelector(store) {
      return selectorFactory(store.dispatch, selectorFactoryOptions);
    }
    // pure模式下用useMemo,否则直接回调
    const usePureOnlyMemo = pure ? useMemo : callback => callback();
    // 这是渲染在页面上的组件
    function ConnectFunction(props) {
      const [propsContext, forwardedRef, wrapperProps] = useMemo(() => {
        // 区分传入的props和控制行为的值(forward ref,替换的context实例)
        const { forwardedRef, ...wrapperProps } = props;
        return [props.context, forwardedRef, wrapperProps];
      }, [props]);
      // 用组件传入的context还是react redux的context
      const ContextToUse = useMemo(() => {
        // 缓存应该使用自带的context还是用户传入的context
        return propsContext &&
          propsContext.Consumer &&
          isContextConsumer(<propsContext.Consumer />)
          ? propsContext
          : Context;
      }, [propsContext, Context]);
      // 从context里取store和subscription
      const contextValue = useContext(ContextToUse);
      // store必须在props或者context里存在,所以需要先判断是不是存在
      // 我们可以直接把store传给组件
      const didStoreComeFromProps =
        Boolean(props.store) &&
        Boolean(props.store.getState) &&
        Boolean(props.store.dispatch);
      const didStoreComeFromContext =
        Boolean(contextValue) && Boolean(contextValue.store);

      // 取出store
      const store = didStoreComeFromProps ? props.store : contextValue.store;

      const childPropsSelector = useMemo(() => {
        // createChildSelector需要store作为参数,在store改变的时候会重新创建
        return createChildSelector(store);
      }, [store]);
      const [subscription, notifyNestedSubs] = useMemo(() => {
        // 这时候组件不会随store变化更新
        if (!shouldHandleStateChanges)
          return NO_SUBSCRIPTION_ARRAY; /* [ null, null ] */
        // 如果组件的store是从props里来的,就不需要传入context里的subscription
        // 通过这个订阅store来让组件更新
        const subscription = new Subscription(
          store,
          // contextValue.subscription这个值,在Provider根是store的subscription,其余情况都是父级的subscription
          // 因为每次connect返回的组件外面包的Provider都使用了新的value
          //   <Provider store={store}>
          //     <Test4> // store的subscription
          //       <Test5 /> // Test4的subscription
          //     </Test4>
          //     <Test6 /> // store的subscription
          //   </Provider>
          didStoreComeFromProps ? null : contextValue.subscription
        );
        // 防止在通知循环中组件被unmount
        const notifyNestedSubs = subscription.notifyNestedSubs.bind(
          subscription
        );
        return [subscription, notifyNestedSubs];
      }, [store, didStoreComeFromProps, contextValue]);

      // 将subscription放入context后的context
      // 因为多层connect嵌套会把subscription传给子级connect
      const overriddenContextValue = useMemo(() => {
        if (didStoreComeFromProps) {
          // 如果组件订阅的是从props里的store,我们不希望子级从这个store里获取任何东西
          return contextValue;
        }
        // 否则将当前组件的subscription放入context里,确保子组件在当前组件更新完之前不会更新
        return {
          ...contextValue,
          subscription,
        };
      }, [didStoreComeFromProps, contextValue, subscription]);
      // 我们需要在redux store更新的时候强制让包装组件更新
      // **正常情况下组件重新的渲染就是因为调用了forceComponentUpdateDispatch,而调用这个就是在订阅的事件中**
      const [
        [previousStateUpdateResult],
        forceComponentUpdateDispatch,
      ] = useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates);
      // 捕获更新产生的错误
      if (previousStateUpdateResult && previousStateUpdateResult.error) {
        throw previousStateUpdateResult.error;
      }
      // 会赋值等于actualChildProps,也就是包括了store,dispatch和传入组件的props
      const lastChildProps = useRef();
      // 传入组件的props
      const lastWrapperProps = useRef(wrapperProps);
      const childPropsFromStoreUpdate = useRef();
      // 控制是否需要通知子级更新
      const renderIsScheduled = useRef(false);
      const actualChildProps = usePureOnlyMemo(() => {
        // 这次渲染也许是因为redux store更新产生了新props触发的
        // 然而,我们也可能在这之后得到父级传入的props
        // 如果我们得到一个新的child props,和一个相同的父级传入的props,我们知道我们应该使用新的child props
        // 但是,如果父级传入了一个新的props,可能会改变child props,所以我们需要重新计算
        // 所以,如果父级的props和上次相同,我们我们会使用从store更新来的新props
        if (
          childPropsFromStoreUpdate.current &&
          wrapperProps === lastWrapperProps.current
        ) {
          return childPropsFromStoreUpdate.current;
        }
        return childPropsSelector(store.getState(), wrapperProps);
        // 主要因为previousStateUpdateResult的改变,才会重新计算actualChildProps
      }, [store, previousStateUpdateResult, wrapperProps]);
      // useIsomorphicLayoutEffectWithArgs会根据是服务端还是浏览器端来决定到底调用useEffect还是useLayoutEffect
      // 这里主要是初始化值,用做以后更新时的对比
      // 还有就是调用自身的notifyNestedSubs,让子组件也更新
      useIsomorphicLayoutEffectWithArgs(captureWrapperProps, [
        lastWrapperProps,
        lastChildProps,
        renderIsScheduled,
        wrapperProps,
        actualChildProps,
        childPropsFromStoreUpdate,
        notifyNestedSubs,
      ]);
      // 只会在store或者subscription改变时候重新订阅
      // 这里主要绑定订阅事件
      useIsomorphicLayoutEffectWithArgs(
        subscribeUpdates,
        [
          shouldHandleStateChanges,
          store,
          subscription,
          childPropsSelector,
          lastWrapperProps,
          lastChildProps,
          renderIsScheduled,
          childPropsFromStoreUpdate,
          notifyNestedSubs,
          forceComponentUpdateDispatch,
        ],
        [store, subscription, childPropsSelector]
      );
      // 下面2个组件用useMemo来优化
      const renderedWrappedComponent = useMemo(
        () => <WrappedComponent {...actualChildProps} ref={forwardedRef} />,
        [forwardedRef, WrappedComponent, actualChildProps]
      );
      const renderedChild = useMemo(() => {
        if (shouldHandleStateChanges) {
          // 如果组件订阅了store的更新,我们需要把它的subscription传递给子级
          // 也就是同样的context使用不同的值
          return (
            <ContextToUse.Provider value={overriddenContextValue}>
              {renderedWrappedComponent}
            </ContextToUse.Provider>
          );
        }

        return renderedWrappedComponent;
      }, [ContextToUse, renderedWrappedComponent, overriddenContextValue]);
      return renderedChild;
    }
    // pure时用React.memo优化
    const Connect = pure ? React.memo(ConnectFunction) : ConnectFunction;

    Connect.WrappedComponent = WrappedComponent;
    Connect.displayName = displayName;

    // 如果forwardRef开启,则需要把子级的ref传递出来
    if (forwardRef) {
      const forwarded = React.forwardRef(function forwardConnectRef(
        props,
        ref
      ) {
        return <Connect {...props} forwardedRef={ref} />;
      });
      forwarded.displayName = displayName;
      forwarded.WrappedComponent = WrappedComponent;
      // 拷贝静态方法并返回
      return hoistStatics(forwarded, WrappedComponent);
    }
    return hoistStatics(Connect, WrappedComponent);
  };
}

复制代码

(为什么连代码高亮都乱了,如果真的有人看可以看我的博客,代码高亮会好一些)

看完整体connectAdvanced后,还是有1个问题没想明白

  • 为什么组件要分层级从上至下订阅,而不是直接订阅storesubscribe

因为在useSelectorhooks方法里,没传递context,订阅的不都是Provider吗?不就没有了connect()的订阅层级了

希望有大佬能解答这个小小的疑惑


整个react-redux的源码绕来绕去,真的挺复杂的,如果有疑问大家可以互相交流

从第一天一脸懵逼到第七天基本搞明白,甚至写了一个简易版,还是很高兴的

最后,祝大家身体健康,工作顺利!

欢迎大家关注我的公众号~

这篇关于React-Redux原理简析与源码解析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!