天天看點

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

未完待續~