react-redux 的 connect 和 Provider的原理

react-redux提供connect和Provider將react和redux鏈接起來。react

  • connect:用於建立容器組件,可使容器組件訪問到Provider組件經過context提供的store,並將mapStateToProps和mapDispatchToProps返回的state和dispatch傳遞給UI組件。
  • Provider:經過context向子組件提供store

一、connect和Provider的使用

// App.jsx
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import createStore from 'redux'
import reducer from './reducers'
import Container from './Container'

const store = createStore(reducer)
const App = () => {
    return (
        <Provider store={store}>
            <Container />
        </Provider>
    )
}

render(<App />, document.getElementById('app'))

容器組件git

// Container.jsx
import React from 'react'
import { connect } from 'react-redux'

const mapStateToProps = (state, ownProps) => ({})

const mapDispatchToProps = (dispatch, ownProps) => ({})

export default connect(mapStateToProps, mapDispatchToProps)(Demo)

二、源碼解析

先看一看react-redux包的目錄結構,其中es目錄適用於ES模塊導入,lib適用於commonjs模塊導入
image.pnggithub

2.一、Provider源碼解析

Provider組件在Provider.js裏面定義,僅有短短几十行代碼,核心代碼以下:redux

import { ReactReduxContext } from './Context';

function Provider(_ref) {
  var store = _ref.store, // 獲取組件綁定的store
      context = _ref.context,
      children = _ref.children; // 獲取子組件
  // contextValue的值爲{store, subscription}
  var contextValue = useMemo(function () {
    var subscription = new Subscription(store);
    subscription.onStateChange = subscription.notifyNestedSubs;
    return {
      store: store,
      subscription: subscription
    };
  }, [store]);
  var previousState = useMemo(function () {
    return store.getState();
  }, [store]);
  useEffect(function () {
    var subscription = contextValue.subscription;
    subscription.trySubscribe();

    if (previousState !== store.getState()) {
      subscription.notifyNestedSubs();
    }

    return function () {
      subscription.tryUnsubscribe();
      subscription.onStateChange = null;
    };
  }, [contextValue, previousState]);
  // 若是Provider組件上綁定了context就是用綁定的context,若是沒有綁定context,就會本身生成context
  // children爲嵌套在Provider裏層的子組件
  var Context = context || ReactReduxContext;
  return React.createElement(Context.Provider, {
    value: contextValue
  }, children);
}

export default Provider;

源碼中使用了useMemo鉤子函數,只有在第二個參數發生變化時,第一個參數函數纔會執行,能夠提高代碼執行性能,避免每次組件渲染都要執行函數。詳情能夠去查看官網,這裏製做簡單介紹。segmentfault

var Context = context || ReactReduxContext;
return React.createElement(Context.Provider, {
    value: contextValue
}, children);

咱們看看這部分代碼,若是Provider組件上綁定了context就是用綁定的context,若是沒有綁定context,就會本身生成context。ReactReduxContext的生成在Context.js中:數組

import React from 'react';
export var ReactReduxContext =
/*#__PURE__*/
React.createContext(null);

if (process.env.NODE_ENV !== 'production') {
  ReactReduxContext.displayName = 'ReactRedux';
}

export default ReactReduxContext;

有了context就能夠向子組件提供store。架構

<Provider store={store}>
    <Container />
</Provider>
// 等價於
<Provider store={store}>
    <Context.Provider value={{value: contextValue}}>
        <Container />
    </Context.Provider>
</Provider>

打開react devtool能夠看到最外層組件爲<Provider>,裏層的子組件由<ReactRedux.Provider>組件包裹
image.pngapp

2.二、connect源碼解析

connect使用方式以下:dom

connect(mapStateToProps, mapDispatchToProps)(Demo)

能夠猜測到connect(mapStateToProps, mapDispatchToProps)這部分將返回一個高階組件,這個高階組件的做用就是將mapStateToProps返回的state和mapDispatchToProps返回的dispatch經過props傳遞給Demo。咱們經過源碼來驗證一下猜測是否正確。ide

connect函數在connect.js中實現,函數架子大概就是以下樣子:

export function createConnect(_temp) {
  // coding...
  return function connect(mapStateToProps, mapDispatchToProps, mergeProps, _ref2) {
    // coding...
    return connectHOC(selectorFactory, options);
  };
}
export default createConnect();

connectHOC函數執行返回的是一個高階組件wrapWithConnect(WrappedComponent),它在connectAdvanced.js中實現,connectAdvanced這個函數就是connectHOC。

export default function connectAdvanced(selectorFactory, _ref) {
  // coding...
  return function wrapWithConnect(WrappedComponent) {
    // coding...
    function createChildSelector(store) {
      return selectorFactory(store.dispatch, selectorFactoryOptions);
    }
    // coding...
    function ConnectFunction(props) {
      // coding...
      
      // 獲取context對象
      var ContextToUse = useMemo(function () {
        return propsContext && propsContext.Consumer && isContextConsumer(React.createElement(propsContext.Consumer, null)) ? propsContext : Context;
      }, [propsContext, Context]); 
      
      // 獲取Context.Provider綁定的value值{store,subscription}
      var contextValue = useContext(ContextToUse);
      
      // 獲取store
      var store = didStoreComeFromProps ? props.store : contextValue.store;
      // childPropsSelector返回一個函數(),接受store.getState()和props
      var childPropsSelector = useMemo(function () {
        return createChildSelector(store);
      }, [store]);
      
      // 這裏執行childPropsSelector,將store.getState()和props傳遞進去,而後mapStateToProps接受到state和props,至於dispatch,在執行selectorFactory(store.dispatch, selectorFactoryOptions);就傳遞進去了。
      var actualChildProps = usePureOnlyMemo(function () {
        if (childPropsFromStoreUpdate.current && wrapperProps === lastWrapperProps.current) {
          return childPropsFromStoreUpdate.current;
        }
        return childPropsSelector(store.getState(), wrapperProps);
      }, [store, previousStateUpdateResult, wrapperProps]);
      
      // actualChildProps獲得的就是mapStateToProps返回的state,把它放在props中傳遞給UI組件
      var renderedWrappedComponent = useMemo(function () {
        return React.createElement(WrappedComponent, _extends({}, actualChildProps, {
          ref: forwardedRef
        }));
      }, [forwardedRef, WrappedComponent, actualChildProps]);
      
      
      var renderedChild = useMemo(function () {
        // shouldHandleStateChanges控制是否應該訂閱redux store中的state變化。
        if (shouldHandleStateChanges) {
          // 訂閱redux store中的state變化,返回ContextToUse.Provider嵌套組件
          return React.createElement(ContextToUse.Provider, {
            value: overriddenContextValue
          }, renderedWrappedComponent);
        }
        // 不須要訂閱redux store中的state變化就直接返回UI組件
        return renderedWrappedComponent;
      }, [ContextToUse, renderedWrappedComponent, overriddenContextValue]);
      return renderedChild;
    }
    // React.memo用於建立一個純函數組件,跟PureComponent同樣,但React.memo做用於function component,而PureComponent做用於class component。使用純函數組件最大的做用就是隻有props變化時組件纔會從新渲染,能夠提升渲染性能。
    var Connect = pure ? React.memo(ConnectFunction) : ConnectFunction;
    Connect.WrappedComponent = WrappedComponent;
    Connect.displayName = displayName;

    if (forwardRef) {
      var forwarded = React.forwardRef(function forwardConnectRef(props, ref) {
        return React.createElement(Connect, _extends({}, props, {
          forwardedRef: ref
        }));
      });
      forwarded.displayName = displayName;
      forwarded.WrappedComponent = WrappedComponent;
      return hoistStatics(forwarded, WrappedComponent);
    }
    // hoistStatics是hoist-non-react-statics包的導出,用於將組件中非react自帶的靜態方法複製到另外一個組件。該包通常用於定義HOC中,由於當你給一個組件添加一個HOC時,原來的組件會被一個container的組件包裹,這意味着新的組件不會有原來組件任何靜態方法。參考:https://zhuanlan.zhihu.com/p/36178509
    return hoistStatics(Connect, WrappedComponent);
  };
}

connectHOC(selectorFactory, options)selectorFactory函數傳遞到connectAdvanced(selectorFactory, _ref)中,在ConnectFunction(props)函數組件中調用createChildSelector(store),而後調用selectorFactory(store.dispatch, selectorFactoryOptions);selectorFactory函數是connect中的核心API,它的實如今selectorFactory.js文件中,selectorFactory就是下面的導出。

export default function finalPropsSelectorFactory(dispatch, _ref2) {
  var initMapStateToProps = _ref2.initMapStateToProps,
      initMapDispatchToProps = _ref2.initMapDispatchToProps,
      initMergeProps = _ref2.initMergeProps,
      options = _objectWithoutPropertiesLoose(_ref2, ["initMapStateToProps", "initMapDispatchToProps", "initMergeProps"]);

  var mapStateToProps = initMapStateToProps(dispatch, options);
  var mapDispatchToProps = initMapDispatchToProps(dispatch, options);
  var mergeProps = initMergeProps(dispatch, options);

  if (process.env.NODE_ENV !== 'production') {
    verifySubselectors(mapStateToProps, mapDispatchToProps, mergeProps, options.displayName);
  }

  var selectorFactory = options.pure ? pureFinalPropsSelectorFactory : impureFinalPropsSelectorFactory;
  // 
  return selectorFactory(mapStateToProps, mapDispatchToProps, mergeProps, dispatch, options);
}

pureFinalPropsSelectorFactory函數實現:

export function pureFinalPropsSelectorFactory(mapStateToProps, mapDispatchToProps, mergeProps, dispatch, _ref) {
  var areStatesEqual = _ref.areStatesEqual,
      areOwnPropsEqual = _ref.areOwnPropsEqual,
      areStatePropsEqual = _ref.areStatePropsEqual;
  var hasRunAtLeastOnce = false;
  var state;
  var ownProps;
  var stateProps;
  var dispatchProps;
  var mergedProps;

  function handleFirstCall(firstState, firstOwnProps) {
    state = firstState;
    ownProps = firstOwnProps;
    stateProps = mapStateToProps(state, ownProps);
    dispatchProps = mapDispatchToProps(dispatch, ownProps);
    mergedProps = mergeProps(stateProps, dispatchProps, ownProps);
    hasRunAtLeastOnce = true;
    return mergedProps;
  }

  function handleNewPropsAndNewState() {}

  function handleNewProps() {}

  function handleNewState() {}

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

selectorFactory的做用就是將鏈接store的ConnectFunction組件中獲取的state、props傳遞給MapStateToProps和將獲取的dispatch傳遞給mapDispatchToProps。而後MapStateToProps和mapDispatchToProps的返回值會在ConnectFunction組件中使用props傳遞給UI組件。

wrapWithConnect(WrappedComponent)返回一個新的、鏈接到store的ConnectFunction(props)函數組件,該組件內部會根據shouldHandleStateChanges字段判斷是否須要監聽redux store中state的變化,若是須要就返回ContextToUse.Provider包裹UI組件的子組件,ContextToUse.Provider爲組組件提供從新構造的overriddenContextValue,若是不須要監聽redux store中state的變化,就返回UI組件爲子組件。就如第一部份內容例子,Brother組件不須要state,Sister組件須要state,那麼Sister組件就會用ContextToUse.Provider包裹着。整個組件架構就變成以下樣子:

image.png

Memo表示該組件爲純函數組件

這三篇文章很是值得一讀,參考:
https://github.com/MrErHu/blo...
https://juejin.im/post/59772a...
https://segmentfault.com/a/11...

相關文章
相關標籤/搜索