React-Redux源码剖析


TL;DR;

  • Connect本质上是一个高阶组件
  • Connect会对组件做缓存控制
  • Connect使用Context做全局状态的通信

Overview

import Provider from './components/Provider'
import connectAdvanced from './components/connectAdvanced'
import { ReactReduxContext } from './components/Context'
import connect from './connect/connect'

export { Provider, connectAdvanced, ReactReduxContext, connect }

如上,react-redux向外暴露了四个API,分别是ProviderConnectAdvancedReactReduxContextconnect

Provider

Provider是一个React Component,在组件didMount的时候订阅reduxstore,在willUnMount的时候接触订阅,每次redux state改变的时候,会调用Provider中的setState,使得组件刷新,我们来看看部分源代码:

    constructor(props) {
    super(props)

    const { store } = props

    this.state = {
      storeState: store.getState(),
      store
    }
  }

在组件初始化的时候,Providerprops上获取redux上的store,并初始化state

同时在组件didMount的时候,这里会向reduxstore上注册callback函数:

    componentDidMount() {
    this._isMounted = true
    this.subscribe()
  }
  subscribe() {
    const { store } = this.props

    this.unsubscribe = store.subscribe(() => {
      const newStoreState = store.getState()

      if (!this._isMounted) {
        return
      }

      this.setState(providerState => {
        // If the value is the same, skip the unnecessary state update.
        if (providerState.storeState === newStoreState) {
          return null
        }

        return { storeState: newStoreState }
      })
    })

    // Actions might have been dispatched between render and mount - handle those
    const postMountStoreState = store.getState()
    if (postMountStoreState !== this.state.storeState) {
      this.setState({ storeState: postMountStoreState })
    }
  }

这里在subscribe函数的最后几行的代码,是用来处理在组件Provider如果在初次render的时候,如果有actiondispatch,这个时候还没有subscribe,但是这时候我们的数据需要刷新,所以这个时候需要去取一次最新数据,如果发现有数据变化,使用setState,触发组件刷新。

render中使用Context.Providercontext中的内容,即我们这里的state注入到children组件中。

从上面的代码可以看到,如果reduxState发生了变化,会触发顶层组件的刷新,重新render,从而重刷整个应用。

Connect

connect这个函数就是我们经常要使用到的,一般来说在使用redux的时候,Container组件就是一个connect之后的组件。 connect主要的工作就是一是负责从state tree上去取组件要用的数据,另外就是在state变化的时候,重新计算来自state上的props。 我们来看看一个简单版本的connect的实现(connect explain):

// connect() is a function that injects Redux-related props into your component.
// You can inject data and callbacks that change that data by dispatching actions.
function connect(mapStateToProps, mapDispatchToProps) {
  // It lets us inject component as the last step so people can use it as a decorator.
  // Generally you don't need to worry about it.
  return function (WrappedComponent) {
    // It returns a component
    return class extends React.Component {
      render() {
        return (
          // that renders your component
          <WrappedComponent
            {/* with its props  */}
            {...this.props}
            {/* and additional props calculated from Redux store */}
            {...mapStateToProps(store.getState(), this.props)}
            {...mapDispatchToProps(store.dispatch, this.props)}
          />
        )
      }
      
      componentDidMount() {
        // it remembers to subscribe to the store so it doesn't miss updates
        this.unsubscribe = store.subscribe(this.handleChange.bind(this))
      }
      
      componentWillUnmount() {
        // and unsubscribe later
        this.unsubscribe()
      }
    
      handleChange() {
        // and whenever the store state changes, it re-renders.
        this.forceUpdate()
      }
    }
  }
}

如上就是connect一个简单版本的实现,connect是一个包装过的高阶组件的函数,在第一层接受一个selector函数,接收reduxstate和传给组件的props, 然后将需要包装的组件传给返回后的函数,最后返回的组件中就可以从props上拿到redux state上的数据了,上面这个是一个简陋的connect实现,我们来看看react redux真正的实现。

我们先来看看connect是如何解析我们传入的参数的,connectmap*传入的格式支持直接传入函数,也可以传入一个obejct,来看看react-redux是如何支持不同的输入的。

// createConnect with default args builds the 'official' connect behavior. Calling it with
// different options opens up some testing and extensibility scenarios
export function createConnect({
  connectHOC = connectAdvanced,
  mapStateToPropsFactories = defaultMapStateToPropsFactories,
  mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
  mergePropsFactories = defaultMergePropsFactories,
  selectorFactory = defaultSelectorFactory
}{
	......
}

export default createConnect()

如上,是connect.js文件暴露出来的connect接口,外面用的时候,是使用的createConnect的返回值。 在这里createConnect使用了默认参数,分别是:

  • connectAdvanced
  • defaultMapStateToPropsFactories
  • defaultMapDispatchToPropsFactories
  • defaultMergePropsFactories
  • defaultSelectorFactory

我们这里先看和props相关的defaultMapStateToPropsFactoriesdefaultMapDispatchToPropsFactories

const initMapStateToProps = match(
      mapStateToProps,
      mapStateToPropsFactories,
      'mapStateToProps'
    )
const initMapDispatchToProps = match(
      mapDispatchToProps,
      mapDispatchToPropsFactories,
      'mapDispatchToProps'
    )

当调用connect的时候,这里会调用match函数,来辨别出我们传入的mapStateToPropsmapDispatchToProps是函数还是object

function match(arg, factories, name) {
  for (let i = factories.length - 1; i >= 0; i--) {
    const result = factories[i](arg)
    if (result) return result
  }

  return (dispatch, options) => {
    throw new Error(
      `Invalid value of type ${typeof arg} for ${name} argument when connecting component ${
        options.wrappedComponentName
      }.`
    )
  }
}

上面就是match函数的代码,这里会将我们传入的map*函数作为参数,传入给mapDispatchToPropsFactories这个工厂函数,我们来这个工厂函数的实现。

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]

如上是mapStateToPropsFactories的实现,这里export出去的是一个array,分别是当mapStateToProps是函数和没有传入的时候,最后返回的是wrapMapToPropsFunc这个函数包装后的函数。

export function wrapMapToPropsFunc(mapToProps, methodName) {
  return function initProxySelector(dispatch,{ displayName }){
	......
}

这里wrapMapToPropsFuc,返回值是一个(dispatch, { displayName}) => {}的接口,这里就是工厂函数生成的函数的接口,这个函数也是被mapDispatchToProps复用的,生成统一的接口函数。

import { bindActionCreators } from 'redux'
import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps'

export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) {
  return typeof mapDispatchToProps === 'function'
    ? wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps')
    : undefined
}

export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {
  return !mapDispatchToProps
    ? wrapMapToPropsConstant(dispatch => ({ dispatch }))
    : undefined
}

export function whenMapDispatchToPropsIsObject(mapDispatchToProps) {
  return mapDispatchToProps && typeof mapDispatchToProps === 'object'
    ? wrapMapToPropsConstant(dispatch =>
        bindActionCreators(mapDispatchToProps, dispatch)
      )
    : undefined
}

export default [
  whenMapDispatchToPropsIsFunction,
  whenMapDispatchToPropsIsMissing,
  whenMapDispatchToPropsIsObject
]

如上是mapDispatchToProps的代码,mapDisptchToProps支持函数、Object、或者不传任何参数,和mapStateToProps一样最后也是返回(dispatch, { options }) => {}。在将map*函数初始化好之后,后面就是调用一个高阶组件函数,用来接收我们想要从redux上获取数据的组件。

    return connectHOC(selectorFactory, {
      // used in error messages
      methodName: 'connect',

      // used to compute Connect's displayName from the wrapped component's displayName.
      getDisplayName: name => `Connect(${name})`,

      // if mapStateToProps is falsy, the Connect component doesn't subscribe to store state changes
      shouldHandleStateChanges: Boolean(mapStateToProps),

      // passed through to selectorFactory
      initMapStateToProps,
      initMapDispatchToProps,
      initMergeProps,
      pure,
      areStatesEqual,
      areOwnPropsEqual,
      areStatePropsEqual,
      areMergedPropsEqual,

      // any extra options args can override defaults of connect or connectAdvanced
      ...extraOptions
    })

上面的代码就是connect(map1, map2)的返回值,这里调用了connectHOC(connectAdvanced),我们来看看connectAdvanced函数的代码:

export default function connectAdvanced(selectorFactory, {.../* options object*/}) {
	......
	return return function wrapWithConnect(WrappedComponent) { ...}
}

上面的代码是connectAdvanced函数的接口和返回值,这里接受selectorFactoryoptions设置,这里的selectorFactory函数就是将我们在上一步调用初始化好的map*函数由(dispatch, { options }) => {}的形式转换为(stateOrDispatch, ownProps) => {}的形式。

export default function finalPropsSelectorFactory(
  dispatch,
  { initMapStateToProps, initMapDispatchToProps, initMergeProps, ...options }
) {
  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
    )
  }

  const selectorFactory = options.pure
    ? pureFinalPropsSelectorFactory
    : impureFinalPropsSelectorFactory

  return selectorFactory(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    dispatch,
    options
  )
}

上面的代码就是selectorFactory函数的实现,这里首先调用了由map工厂函数生成的函数,返回的函数就是由wrapMapToPropsFunc返回的函数,这里我们来看看wrapMapToPropsFunc的实现:

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

    // allow detectFactoryAndVerify to get ownProps
    proxy.dependsOnOwnProps = true

    proxy.mapToProps = function detectFactoryAndVerify(
      stateOrDispatch,
      ownProps
    ) {
      proxy.mapToProps = mapToProps
      proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)
      let props = proxy(stateOrDispatch, ownProps)

      if (typeof props === 'function') {
        proxy.mapToProps = props
        proxy.dependsOnOwnProps = getDependsOnOwnProps(props)
        props = proxy(stateOrDispatch, ownProps)
      }

      if (process.env.NODE_ENV !== 'production')
        verifyPlainObject(props, displayName, methodName)

      return props
    }

    return proxy
  }
}

这里的wrapMapToPropsFunc最终返回的是一个proxy函数,这个函数的接口是(dispatchOrState, ownProps) => {},这也我们的map*函数的接口相同,这里proxy函数主要是处理在第一次运行的时候,如果我们提供的map*函数的返回值是map函数这种情况,另外就是检测最后我们提供的map*函数的返回值是一个plain object的情况。

在拿到真正的map*函数后,会判断传入的optionspure的值来决定使用哪一个SelectorFactory函数,默认情况下会使用pureFinalPropsSelectorFactory,如果purefalse的话会使用impureFinalPropsSelectorFactory,前一个函数缓存了前一次的stateownPropsstatePropsdispatchPropsmergedProps,在第一次调用connect的时候,会设置好缓存,在后续的调用中会比较缓存,如果缓存的引用没变,就不会调用map*函数生成新的props。具体的实现代码如下:

export function pureFinalPropsSelectorFactory(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
  dispatch,
  { areStatesEqual, areOwnPropsEqual, areStatePropsEqual }
) {
  let hasRunAtLeastOnce = false
  let state
  let ownProps
  let stateProps
  let dispatchProps
  let 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() {
    stateProps = mapStateToProps(state, ownProps)

    if (mapDispatchToProps.dependsOnOwnProps)
      dispatchProps = mapDispatchToProps(dispatch, ownProps)

    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
    return mergedProps
  }

  function handleNewProps() {
    if (mapStateToProps.dependsOnOwnProps)
      stateProps = mapStateToProps(state, ownProps)

    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

    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

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

如上是pureFinalPropsSelectorFactory的实现,这里在第一次调用的时候产生一份缓存,后续的调用会检查缓存。

上面的一连串过程就是props如何产生,以及props的刷新过程。简要的来说,这里的几个不同的factory函数,主要的工作是统一接口,校验传进来的参数,函数接口的转换形式如下:

(dispatch, options) => (mapOrDispatch, ownProps) => finalProps

上面就是这几个factory函数的主要工作。

我们继续来看connect后续的代码的主要工作是返回一个高阶组件来包装我们的组件:

export default function connectAdvanced(selectoryFactory, {
	...
	/*options object*/
}) {
	return withConnect(WrappedComponent) {
		......
	}
}

这里对这个高阶组件做了几个小的处理,一是对于forwardRef的处理,而是对Context的处理。

    if (forwardRef) {
      const forwarded = React.forwardRef(function forwardConnectRef(
        props,
        ref
      ) {
        return <Connect wrapperProps={props} forwardedRef={ref} />
      })

      forwarded.displayName = displayName
      forwarded.WrappedComponent = WrappedComponent
      return hoistStatics(forwarded, WrappedComponent)
    }

如上,如果forwardReftrue的时候,这个时候,会讲ref挂到connect包的底层组件上;另外就是对context的处理了

render() {
        const ContextToUse =
          this.props.context &&
          this.props.context.Consumer &&
          isContextConsumer(<this.props.context.Consumer />)
            ? this.props.context
            : Context

        return (
          <ContextToUse.Consumer>
            {this.indirectRenderWrappedComponent}
          </ContextToUse.Consumer>
        )
      }

connect的第三个参数支持注入我们自己的context,如上的代码,如果我们在mergeProps中注入了我们自己的context,会优先使用我们自己的context,否则就使用Provider上提供的context。在之前讲的到,selectorFactory会讲wrapMapToProps包装后的函数(dispatch, options) => {} 转换为(stateOrDispatch, ownProps) => {}。在代码里的体现如下:

render() {
        const ContextToUse =
          this.props.context &&
          this.props.context.Consumer &&
          isContextConsumer(<this.props.context.Consumer />)
            ? this.props.context
            : Context

        return (
          <ContextToUse.Consumer>
            {this.indirectRenderWrappedComponent}
          </ContextToUse.Consumer>
        )
      }	

connectrender方法中,调用了this.indirectRenderWrappedComponent,这个方法实际上就是makeDerivedPropsSelector返回的函数,我们来看看这个方法的代码:

 function makeDerivedPropsSelector() {
      let lastProps
      let lastState
      let lastDerivedProps
      let lastStore
      let lastSelectorFactoryOptions
      let sourceSelector

      return function selectDerivedProps(
        state,
        props,
        store,
        selectorFactoryOptions
      ) {
        if (pure && lastProps === props && lastState === state) {
          return lastDerivedProps
        }

        if (
          store !== lastStore ||
          lastSelectorFactoryOptions !== selectorFactoryOptions
        ) {
          lastStore = store
          lastSelectorFactoryOptions = selectorFactoryOptions
          sourceSelector = selectorFactory(
            store.dispatch,
            selectorFactoryOptions
          )
        }

        lastProps = props
        lastState = state

        const nextProps = sourceSelector(state, props)

        lastDerivedProps = nextProps
        return lastDerivedProps
      }
    }

可以看到在第一次运行,更换store或更换selectorFactoryOptions的时候,会重新获取storeselectorFactoryOptions,这块函数就是每次context刷新或者props change的时候,调用当前的map*函数,生成新的props或者是使用旧的props。最后调用makeChildElementSelector将计算好的props,要渲染的组件,以及要转发的ref传进去,可以看到这里的刷新也是有一层缓存的,如果传入的这些东西还是上一次的话,这里还是会返回上一次渲染的组件。

   function makeChildElementSelector() {
      let lastChildProps, lastForwardRef, lastChildElement, lastComponent

      return function selectChildElement(
        WrappedComponent,
        childProps,
        forwardRef
      ) {
        if (
          childProps !== lastChildProps ||
          forwardRef !== lastForwardRef ||
          lastComponent !== WrappedComponent
        ) {
          lastChildProps = childProps
          lastForwardRef = forwardRef
          lastComponent = WrappedComponent
          lastChildElement = (
            <WrappedComponent {...childProps} ref={forwardRef} />
          )
        }

        return lastChildElement
      }
    }

在没有转发ref的情况下,connect组件会返回:

   return hoistStatics(Connect, WrappedComponent)

这里的hoistStatics函数是为了将WrapperComponent上的静态属性复制到Connect的组件上;原因在于,如果原始的组件上有一个静态方法,在connect之后的组件暴露出去用的时候,这个组件实际上是访问不到的,所以要把一些静态属性拷到高阶组件上去。


兴趣遍地都是,坚持和持之以恒才是稀缺的


Written by@wang yao
I explain with words and code.

GitHub