React-redux源码分析

1,329 阅读7分钟

React-redux源码总共分为两部分,第一是Provider,第二是connect

Provider

Provider比较简单,主要代码如下:

 class Provider extends Component {
        getChildContext() {
          return { [storeKey]: this[storeKey], [subscriptionKey]: null }
        }

        constructor(props, context) {
          super(props, context)
          this[storeKey] = props.store;
        }

        render() {
          return Children.only(this.props.children)
        }
    }

即通过context api将store传到子组件里面去。

connect

connect算是一个比较有难度的部分,函数原型是:

connect(mapStateToProps,mapDispatchToProps,[mergeProps]) => (WrappedComponent) => HOC

我们尝试对其进行由上到下的分析:

入口

函数入口就是一个三阶函数:

export function createConnect({
    connectHOC,
    mapStateToPropsFactories,
    mapDispatchTpropsFactories,
    mergePropsFactories,
    selectFactory
}) => connect(mapStateToProps,mapDispatchToProps,mergeProps, {/*...*/}) =>  {

    const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps')
    const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps')
    const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
    
    return connectHOC(selectorFactory, {
        initMapStateToProps,
        initMapDispatchToProps,
        initMergeProps,
        //...
    })
}
export default createConnect()

写的其实比较绕,但是理解起来不难,首先createConnect方法只相当于一个包装而已,将高阶组件connectHOC,以及一些工厂函数引入到connect的父scope中来。

然后在connect中,通过match函数来选择适当的factory来包装外界传入的参数)生成三个函数: initMapStateToProps,initMapDispatchToPropsinitMergeProps

match函数的源码:

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

这段代码其中arg就是connect函数接收的mapStateToProps等,以倒序的方式寻找第一个返回非空的factory。我们可以先暂且不去深入推敲。

ConnectHOC

connectHOC是整个react-redux的核心,在分析源代码之前,我们先看看一个工具函数:

function makeSelectorStateful(sourceSelector, store) {
  const selector = {
    run: function runComponentSelector(props) {
      try {
        const nextProps = sourceSelector(store.getState(), props)
        if (nextProps !== selector.props || selector.error) {
          selector.shouldComponentUpdate = true
          selector.props = nextProps
          selector.error = null
        }
      } catch (error) {
        selector.shouldComponentUpdate = true
        selector.error = error
      }
    }
  }

  return selector
}

selector主要作用是对redux的state和dispatch与react的props进行合并(mergeProps) 这段代码主要是对sourceSelector进行封装,增加了错误处理,新旧值的比较等。

下面是connectHOC的主要源码:

// ...
export default function connectAdvanced(
  selectorFactory,
  // options object:
  {
    //省略掉的参数: withRef,displayName,renderCountProp,shouldHandleStateChanges,storeKey,getDisplayName
    // 传入selectorFactory中额外的参数
    // 主要包括 `initMapStateToProps`,`initMapDispatchToProps``initMergeProps`
    ...connectOptions
  } = {}
) {
  const subscriptionKey = storeKey + 'Subscription'
  // ...

  return function wrapWithConnect(WrappedComponent) {
    // ... 
    const selectorFactoryOptions = {
      ...connectOptions,
      //... 
      WrappedComponent
    }
    
    // 声明一个React组件
    class Connect extends Component {
      constructor(props, context) {
        super(props, context)
        // ...
        // 从props或者context里面获得redux store
        this.store = props[storeKey] || context[storeKey]
        this.propsMode = Boolean(props[storeKey])
        this.initSelector()
        this.initSubscription()
      }
      
      initSelector() {
        // 结合mapPropsTo...和mergeProps函数处理参数。(state, props) => nextProps
        const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
        // makeSelectorStateful:  包装sourceSelector函数,比较props和nextProps确定shouldComponentUpdate
        this.selector = makeSelectorStateful(sourceSelector, this.store)
        // 更新当前参数,实际上就是ownProps
        this.selector.run(this.props)
      }

      initSubscription() {
        // 如果不需要相应redux state变化,则不需要订阅
        if (!shouldHandleStateChanges) return
        // 获取祖先传下来的订阅对象
        const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]
        
        // 生成当前组件的订阅对象,传入parentSub。
        // 当parentSub不为空的时候,onStateChange实际上是注册到了parentSub的listeners数组,由组件手动调用notifyNestedSubs执行。
        // 否则直接使用store.subscribe注册到redux中,由redux控制执行
        // 这样做能始终保证祖先节点的更新早于子孙节点
        this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))
        this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
      }

      onStateChange() {
        // 更新props
        this.selector.run(this.props)
        if (!this.selector.shouldComponentUpdate) {
          // 如果当前组件不需要更新,那么直接更新子组件
          this.notifyNestedSubs()
        } else {
         // 先更新当前组件,再更新子组件
          this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate
          // dummyState 为空,意味触发react的更新机制
          this.setState(dummyState)
        }
      }
      getChildContext() {
        // 如果store是从props中获得,那么直接将祖先的subscription对象传下去。(仅作为一个传话人,不响应父组件的dispatch事件)
        // 否则,将自身的subscription传下去,形状如下
        // Component A (B's subscription's parentSub == A)
        // |
        //  -> Component B (C's subscription's parentSub == B)
        //     |
        //      -> Component C (E's subscription's parentSub == C)
        //     |
        //     |== Component D (propsMode === true) directly transfer C's sub context to E
        //         |
        //          -> Component E
        // when a dispatch occured in Component A,the callback exec order will be A -> B -> C -> E
        
        const subscription = this.propsMode ? null : this.subscription
        return { [subscriptionKey]: subscription || this.context[subscriptionKey] }
      }

      componentDidMount() {
        if (!shouldHandleStateChanges) return
        // 因为componentWillMount在ssr中执行,而componentDidMount和componentWillUnmount不执行。因此如果将trySubscribe方法放在componentWillMount中的话,在ssr中可能会因为没有调用componentWillUnmount的unsubscribe而内存泄漏
        this.subscription.trySubscribe()
        
        // run即update,更新selector.nextProps,selector.shouldComponentUpdate
        this.selector.run(this.props)
        
        // 如果子组件在componentWillMount的时候dispatch了一个action,因而没有被上文的subscription捕获到,此时的selector就会检测出参数的不一致(shouldComponentUpdate = true),所以需要强制更新
        if (this.selector.shouldComponentUpdate) this.forceUpdate()
      }

      // 更新props
      componentWillReceiveProps(nextProps) {
        this.selector.run(nextProps)
      }

      shouldComponentUpdate() {
        return this.selector.shouldComponentUpdate
      }

      componentWillUnmount() {
        if (this.subscription) this.subscription.tryUnsubscribe()
        
        // 为什么直接使用subscription.notifyNestedSubs而要拷贝出来分别处理:
        // 防止在notify过程中subscription为null,而null.notifyNestedSubs报错
        this.subscription = null
        this.notifyNestedSubs = noop
        
        this.store = null
        this.selector.run = noop
        this.selector.shouldComponentUpdate = false
      }


      notifyNestedSubsOnComponentDidUpdate() {
        // 避免多次调用
        this.componentDidUpdate = undefined
        this.notifyNestedSubs()
      }

      isSubscribed() {
        return Boolean(this.subscription) && this.subscription.isSubscribed()
      }

      addExtraProps(props) {
        // 如果没有额外的参数,直接返回props
        if (!withRef && !renderCountProp && !(this.propsMode && this.subscription)) return props
        // 防御性拷贝
        const withExtras = { ...props }
        if (withRef) withExtras.ref = this.setWrappedInstance
        if (renderCountProp) withExtras[renderCountProp] = this.renderCount++
        if (this.propsMode && this.subscription) withExtras[subscriptionKey] = this.subscription
        return withExtras
      }

      render() {
        const selector = this.selector
        selector.shouldComponentUpdate = false

        if (selector.error) {
          throw selector.error
        } else {
          return createElement(WrappedComponent, this.addExtraProps(selector.props))
        }
      }
    }
    // ...
    
    // 仅在开发环境下支持的的热重载
    if (process.env.NODE_ENV !== 'production') {
      Connect.prototype.componentWillUpdate = function componentWillUpdate() {
        // We are hot reloading!
        if (this.version !== version) {
          this.version = version
          this.initSelector()
          
          let oldListeners = [];

          if (this.subscription) {
            oldListeners = this.subscription.listeners.get()
            this.subscription.tryUnsubscribe()
          }
          this.initSubscription()
          if (shouldHandleStateChanges) {
            this.subscription.trySubscribe()
            oldListeners.forEach(listener => this.subscription.listeners.subscribe(listener))
          }
        }
      }
    }
   // 将WrappedComponent中的非React特定的静态属性(例如propTypes就是React的特定静态属性)赋值到Connect。
   // 作用有点类似于Object.assign,但是仅复制非React特定的静态属性。
    return hoistStatics(Connect, WrappedComponent)
  }
}

至此,大部分逻辑我们已经很清楚了。现在我们来分析一下各种factory所做的事情:

selectorFactory

首先看主体:

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

该函数相当于一个控制器,根据pure选项来决定使用那种具体实现。 如果puretrue,则selectorFactory将会返回一个可以记忆结果的selector,这样才有可能使得connectHOC里面的selector.shouldComponentUpdatefalse。反之,selector将永远返回新的对象,selector.shouldComponentUpdate始终为true

impureFinalPropsSelectorFactory

export function impureFinalPropsSelectorFactory(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
  dispatch
) {
  return function impureFinalPropsSelector(state, ownProps) {
    return mergeProps(
      mapStateToProps(state, ownProps),
      mapDispatchToProps(dispatch, ownProps),
      ownProps
    )
  }
}

直接就很粗暴了

pureFinalPropsSelectorFactory

export function pureFinalPropsSelectorFactory(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
  dispatch,
  { areStatesEqual, areOwnPropsEqual, areStatePropsEqual }
) {
// 闭包,memory之前的结果,仅当有变化的时候才更新
  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() {
    // state 已经 changed
    stateProps = mapStateToProps(state, ownProps)

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

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

  function handleNewProps() {
    // 仅当state 用到了OwnProps才更新
    if (mapStateToProps.dependsOnOwnProps)
      stateProps = mapStateToProps(state, ownProps)
    // 仅当disaptch 用到了OwnProps才更新
    if (mapDispatchToProps.dependsOnOwnProps)
      dispatchProps = mapDispatchToProps(dispatch, ownProps)
      
    
    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
    return mergedProps
  }

  function handleNewState() {
    const nextStateProps = mapStateToProps(state, ownProps)
    // shallow compare state是否改变
    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)
  }
}

看上去复杂许多,但主要还是因为需要区分是否是首次调用而已。

mapStateToProps

mapStateToPropsmapDispatchToProps差不多,大部分都是公共代码(wrapMapToProps),因此我们以mapStateToProps为例:

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
]

跟selectorFactory一样,这里同样有多种解决方案,不过不同的是,这里是直接将所有方案以数组的方式返回,供前面connect中的match函数匹配。

然后我们主要来看一下其中涉及到的几个函数:

wrapMapToPropsConstant

export function wrapMapToPropsConstant(getConstant) {
  return function initConstantSelector(dispatch, options) {
    const constant = getConstant(dispatch, options)
    function constantSelector() { return constant }
    constantSelector.dependsOnOwnProps = false 
    return constantSelector
  }
}

顾名思义,constant不允许结果发生变化的,所以函数里面调用传入的getConstant函数之后,直接返回一个getter函数:contantSelector

wrapMapToPropsFunc

// methodName只用来debug
export function wrapMapToPropsFunc(mapToProps, methodName) {
  // options 只需要 displayname , 也只用来debug
  return function initProxySelector(dispatch, { displayName }) {
    const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
      return proxy.dependsOnOwnProps
        ? proxy.mapToProps(stateOrDispatch, ownProps)
        : proxy.mapToProps(stateOrDispatch)
    }

    proxy.dependsOnOwnProps = true

    proxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch, ownProps) {
      proxy.mapToProps = mapToProps
      proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)
      let props = proxy(stateOrDispatch, ownProps)
      // 如果mapToProps执行后返回的结果还是一个函数,则继续递归调用,直到它扁平为止
      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
  }
}

这里主要运用了代理的方法,挺巧妙的对mapToProps方法进行了包装,增加了dependsOnOwnProps这个静态成员,判断方法很简单,直接判断Function的参数长度即可:

getDependsOnOwnProps

export function getDependsOnOwnProps(mapToProps) {
  return (mapToProps.dependsOnOwnProps !== null && mapToProps.dependsOnOwnProps !== undefined)
    ? Boolean(mapToProps.dependsOnOwnProps)
    : mapToProps.length !== 1
}

至此,所有代码都通了, cheers!~

参考

  1. react-redux源码解析
  2. react-redux源码分析
  3. 庖丁解牛React-Redux(一): connectAdvanced