天天看点

react-redux connect源码理解

react-redux connect源码理解

    • connect函数源码

connect函数源码

// match函数基本技术介绍 (dispatch, options) => (nextState, nextOwnProps) => nextFinalProps

function connect(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    {
      pure = true,
      areStatesEqual = strictEqual,
      areOwnPropsEqual = shallowEqual,
      areStatePropsEqual = shallowEqual,
      areMergedPropsEqual = shallowEqual,
      ...extraOptions
    } = {}
  ) {
    const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps')
    const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps')
    const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')

    return connectHOC(...)
  }
           

可以看到的是在connect函数中初始化处理了入参mapStateToProps,mapDispatchToProps,mergeProps,让他们符合初始化函数的形式

  • 初始化函数形式
(dispatch, options) => (nextState, nextOwnProps) => nextFinalProps
           

处理入参之后就返回了一个React 高阶组件(HOC)

connectHOC

  • connectHOC的默认入参有
    react-redux connect源码理解
  • connectHOC默认值connectAdvanced函数的代码
import hoistStatics from 'hoist-non-react-statics' // 复制子组件信息到父组件
import invariant from 'invariant' // 输出错误信息
import { Component, createElement } from 'react'
import { isValidElementType } from 'react-is' // 判断是否是react元素的方法

import Subscription from '../utils/Subscription' // 实现发布订阅的类函数
import { storeShape, subscriptionShape } from '../utils/PropTypes' // store的类型和subscription的类型


let hotReloadingVersion = 0 // 重载版本
const dummyState = {} // 假的state?
function noop() {} // 默认函数
// 通过store和合并之后的selector计算得到最终的props
function makeSelectorStateful(sourceSelector, store) {
  // wrap the selector in an object that tracks its results between runs.
  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
}

export default function connectAdvanced(
  selectorFactory, // 合并connect selector参数(mapState,mapDispath,mergeProps)的一个方法
  {
    getDisplayName = name => `ConnectAdvanced(${name})`,
    methodName = 'connectAdvanced',
    renderCountProp = undefined, // 渲染技术
    shouldHandleStateChanges = true, // 状态改变时是否更新
    // the key of props/context to get the store
    storeKey = 'store', // 关键key: store, 用于连接redux
    // if true, the wrapped element is exposed by this HOC via the getWrappedInstance() function.
    withRef = false,
    ...connectOptions
  } = {}
) {
  const subscriptionKey = storeKey + 'Subscription'
  const version = hotReloadingVersion++

  const contextTypes = {
    [storeKey]: storeShape,
    [subscriptionKey]: subscriptionShape,
  }
  const childContextTypes = {
    [subscriptionKey]: subscriptionShape,
  }

  return function wrapWithConnect(WrappedComponent) {
    invariant(
      isValidElementType(WrappedComponent),
      `You must pass a component to the function returned by ` +
      `${methodName}. Instead received ${JSON.stringify(WrappedComponent)}`
    )

    const wrappedComponentName = WrappedComponent.displayName
      || WrappedComponent.name
      || 'Component'

    const displayName = getDisplayName(wrappedComponentName)

    const selectorFactoryOptions = {
      ...connectOptions,
      getDisplayName,
      methodName,
      renderCountProp,
      shouldHandleStateChanges,
      storeKey,
      withRef,
      displayName,
      wrappedComponentName,
      WrappedComponent
    }

    class Connect extends Component {
      constructor(props, context) {
        super(props, context)
	
        this.version = version // 版本?重载的次数记录?
        this.state = {}
        this.renderCount = 0 // 渲染次数
// 获取redux store,优先从父组件获取。正常情况下,connect的组件最好不要传key为store的props
        this.store = props[storeKey] || context[storeKey]
		// 判断是不是父组件就有store,是不是父组件模式
        this.propsMode = Boolean(props[storeKey])
        this.setWrappedInstance = this.setWrappedInstance.bind(this)

        invariant(this.store,
          `Could not find "${storeKey}" in either the context or props of ` +
          `"${displayName}". Either wrap the root component in a <Provider>, ` +
          `or explicitly pass "${storeKey}" as a prop to "${displayName}".`
        )
		// 初始化selector
        this.initSelector()
        // 初始化发布订阅的模型
        this.initSubscription()
      }
      
	  // 声明React树节点下方组件可获取的context
      getChildContext() {
        const subscription = this.propsMode ? null : this.subscription
        return { 
        	[subscriptionKey]: subscription || this.context[subscriptionKey] 
        }
      }
		
	  initSelector() {
	  // 初始selector, 这里绑定了this.store.dispatch函数和mapStateToProps三个函数的关系
        const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
        this.selector = makeSelectorStateful(sourceSelector, this.store)
        // 初始化执行
        this.selector.run(this.props)
      }

      initSubscription() {
      	// 更新不需要订阅就不
        if (!shouldHandleStateChanges) return
        
        // 获取发布订阅的方法对象,可以看redux或的具体实现
        const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]; 
        // this.onStateChange订阅store的变化
        this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))
        // 触发更新propsmode为true时候的更新函数?
        this.notifyNestedSubs =	 this.subscription.notifyNestedSubs.bind(this.subscription)
      }
      
	  // 订阅store更新函数
      onStateChange() {
        this.selector.run(this.props)

        if (!this.selector.shouldComponentUpdate) {
          this.notifyNestedSubs()
        } else {
          this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate
          this.setState(dummyState)
        }
      }
      // 组件创建后更新props相关
      componentDidMount() {
        if (!shouldHandleStateChanges) return
        
        this.subscription.trySubscribe()
        this.selector.run(this.props)
        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()
        this.subscription = null
        this.notifyNestedSubs = noop
        this.store = null
        this.selector.run = noop
        this.selector.shouldComponentUpdate = false
      }

      getWrappedInstance() {
        invariant(withRef,
          `To access the wrapped instance, you need to specify ` +
          `{ withRef: true } in the options argument of the ${methodName}() call.`
        )
        return this.wrappedInstance
      }

      setWrappedInstance(ref) {
        this.wrappedInstance = ref
      }

      
      notifyNestedSubsOnComponentDidUpdate() {      
        this.componentDidUpdate = undefined
        this.notifyNestedSubs()
      }

      isSubscribed() {
        return Boolean(this.subscription) && this.subscription.isSubscribed()
      }
      // 一些特殊情况处理,withRef, renderCountProp,this.propsMode为true并。。。
      addExtraProps(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))
        }
      }
    }

    /* eslint-enable react/no-deprecated */

    Connect.WrappedComponent = WrappedComponent
    Connect.displayName = displayName
    Connect.childContextTypes = childContextTypes
    Connect.contextTypes = contextTypes
    Connect.propTypes = contextTypes

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

    return hoistStatics(Connect, WrappedComponent)
  }
}
           

未完待续~