阅读react-redux源码(二) - createConnect、match函数的实现

上一节看了Provider组件的实现,主要做的事情就是通过Context透传了来自redux的store和监听store变化的事件对象Subscription的实例。

本节会深入到connect组件的内部查看实现方式,整个connect组件的实现相对复杂,并且巧妙。

通过入口文件可以知道connect组件是/src/connect/connect.js的默认导出。文件内容如下:

import connectAdvanced from '../components/connectAdvanced'
import shallowEqual from '../utils/shallowEqual'
import defaultMapDispatchToPropsFactories from './mapDispatchToProps'
import defaultMapStateToPropsFactories from './mapStateToProps'
import defaultMergePropsFactories from './mergeProps'
import defaultSelectorFactory from './selectorFactory'

/*
  connect is a facade over connectAdvanced. It turns its args into a compatible
  selectorFactory, which has the signature:

    (dispatch, options) => (nextState, nextOwnProps) => nextFinalProps
  
  connect passes its args to connectAdvanced as options, which will in turn pass them to
  selectorFactory each time a Connect component instance is instantiated or hot reloaded.

  selectorFactory returns a final props selector from its mapStateToProps,
  mapStateToPropsFactories, mapDispatchToProps, mapDispatchToPropsFactories, mergeProps,
  mergePropsFactories, and pure args.

  The resulting final props selector is called by the Connect component instance whenever
  it receives new props or store state.
 */
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
      }.`
    )
  }
}

function strictEqual(a, b) {
  return a === b
}

// 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
} = {}) {
  return 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(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
    })
  }
}

export default /*#__PURE__*/ createConnect()

createConnect

首先看看createConnect,关于函数如果有注释先看注释然后主要看函数名,一个好的函数名会表明这个函数是做什么的。再看函数的入参和函数的出参,最后可以看函数的实现方式。

export function createConnect({
  connectHOC = connectAdvanced,
  mapStateToPropsFactories = defaultMapStateToPropsFactories,
  mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
  mergePropsFactories = defaultMergePropsFactories,
  selectorFactory = defaultSelectorFactory
} = {}) {
  return function connect(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    {
      pure = true,
      areStatesEqual = strictEqual,
      areOwnPropsEqual = shallowEqual,
      areStatePropsEqual = shallowEqual,
      areMergedPropsEqual = shallowEqual,
      ...extraOptions
    } = {}
  ) {
  }
}

首先这个函数定义参数的形式有点奇怪,function name({a = 1, b = 2} = {})这种形式。这其实是JS中的命名参数的写法,和Python、Dart之类的语言中提供的命名参数特性类似。入参的赋值和名字有关,而不是位置。

而入参分别是connectHOC还有剩下来的几个factory,分别是生产mapStateToProps的工厂、生产mapDispathToProps的工厂,生产mergeProps的工厂和生产选择器的工厂。

后面createConnect函数会返回connect函数,而这个connect函数就是连接组件用到的组件。

在底部会执行createConnect方法则会得到该方法返回的connect函数,在组件中通过connect函数可以连接到顶层Provider提供的store和Subscribtion实例。

connect函数

return 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(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函数就是我们用于连接store的函数。connect函数的入参分别是 mapStateToPropsmapDispatchToPropsmergeProps和一个配置对象。

首先看下入参的配置对象:

{
    pure = true,
    areStatesEqual = strictEqual,
    areOwnPropsEqual = shallowEqual,
    areStatePropsEqual = shallowEqual,
    areMergedPropsEqual = shallowEqual,
    ...extraOptions
} = {}

配置对象定义了用于对比数据的函数,分别是对比store中的state,对比被包裹组件自身的props,对比被组件选中的store中的state,对比被包裹组件自身的props和来自store的state被选中的state。

这里要注意,我们知道一个连接到store的组件的props会有两个来源,一个是store,包括store中的state和dispatch。还有一个是父组件传递给组件的props。 而要做到性能最优则是接收到新的值时对这些值分别对比,如果没有改变则不需要更新组件,以优化组件性能。

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

这段代码统一处理了三个工厂函数生成目标函数的过程。

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

factories是数组,遍历这个数组,找到一个真的返回值,返回出去,否则返回一个会抛出错误的方法。这是责任链模式,factories是一个链,数组的每一项只处理自身的情况,如果匹配则返回,否则返回undefined通知match继续遍历链。

factories对应的数据结构式[() => (fn || undefined), () => (fn || undefined), ...]

注意:match从右往左遍历factories数组。

整理好相关入参,全部传递给connectHOC,这个高阶组件是整个connect的核心,如何订阅store更新,如何防止store的更新引起不必要的渲染,如何处理ref的获取,如何使用通过props传入的context和store,如何透传父组件传入的props。这些都会在connect中处理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章