React-Redux原理簡析與源碼解析

沒有看過Redux源碼的同窗能夠看看個人上一篇文章Redux原理簡析與源碼解析javascript

本文發佈在個人博客java

本文的github倉庫
react

自從我學習太高階組件,你們流傳的就是connect()就是一個高階組件,因此在我長久的認知中,認爲connect()僅僅是一個從context中篩選出state,傳入給子組件的簡單高階組件git

我我的用redux是比較少的,自從hooks出了之後,就常常用useContext+useReducer來實現簡單的redux的功能,因此也認爲在react-redux裏,connect()會像useReducer同樣,數據從新渲染React控制github

結論固然不是這樣,而且connect()組件也不簡單,能夠說在我打開源碼的第一時間傻眼了,下面就來好好的分析下react-reduxredux

原理簡述

在分析源碼以前,須要先將react-redux的原理簡述一下,不然光幹看代碼仍是會比較懵api

先簡單的畫一個流程瀏覽器

react-redux的核心機制是通知訂閱模式,源碼中有一個Subscription類,它的做用主要是訂閱父級的更新和通知子級的更新,也就是它既能夠訂閱別人,別人也能夠訂閱它,同時能夠通知訂閱它的Subscription緩存

最外層的Provider組件的Context裏包含了的store(也就是咱們傳入的)和生成的Subscription實例,它的Subscription實例訂閱的則是reduxsubscrib()app

當咱們使用了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的源碼繞來繞去,真的挺複雜的,若是有疑問你們能夠互相交流

從第一天一臉懵逼到第七天基本搞明白,甚至寫了一個簡易版,仍是很高興的

最後,祝你們身體健康,工做順利!

歡迎你們關注個人公衆號~

相關文章
相關標籤/搜索