欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

react-redux connect源码理解

程序员文章站 2022-07-03 17:34:48
...

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

未完待续~