react-redux 的 connect 和 Provider的原理

记得要微笑

react-redux提供connect和Provider将react和redux连接起来。

  • connect:用于创建容器组件,可以使容器组件访问到Provider组件通过context提供的store,并将mapStateToProps和mapDispatchToProps返回的state和dispatch传递给UI组件。
  • Provider:通过context向子组件提供store

1、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'))

容器组件

// 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)

2、源码解析

先看一看react-redux包的目录结构,其中es目录适用于ES模块导入,lib适用于commonjs模块导入
image.png

2.1、Provider源码解析

Provider组件在Provider.js里面定义,仅有短短几十行代码,核心代码如下:

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钩子函数,只有在第二个参数发生变化时,第一个参数函数才会执行,可以提升代码执行性能,避免每次组件渲染都要执行函数。详情可以去查看官网,这里制作简单介绍。

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.png

2.2、connect源码解析

connect使用方式如下:

connect(mapStateToProps, mapDispatchToProps)(Demo)

可以猜想到connect(mapStateToProps, mapDispatchToProps)这部分将返回一个高阶组件,这个高阶组件的作用就是将mapStateToProps返回的state和mapDispatchToProps返回的dispatch通过props传递给Demo。我们通过源码来验证一下猜想是否正确。

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...

阅读 7.5k

avatar
记得要微笑
前端工程师

求上而得中,求中而得下,求下而不得

1.6k 声望
4.4k 粉丝
0 条评论
avatar
记得要微笑
前端工程师

求上而得中,求中而得下,求下而不得

1.6k 声望
4.4k 粉丝
文章目录
宣传栏