connectAdvanced

这个方法可以说是connect最核心的实现方法了,所以你要了解connect你必须要了解这个方法,之前我们分别介绍了connect的返回值和各个参数,react-redux暴露出这个方法,让你可以通过传递自定义的参数去覆盖一些默认的功能。

参数

  • selectorFactory
  • connectOptions

使用方式

import * as actionCreators from './actionCreators'
import { bindActionCreators } from 'redux'

function selectorFactory(dispatch) {
  let ownProps = {}
  let result = {}

  const actions = bindActionCreators(actionCreators, dispatch)
  const addTodo = text => actions.addTodo(ownProps.userId, text)

  return (nextState, nextOwnProps) => {
    const todos = nextState.todos[nextOwnProps.userId]
    const nextResult = { ...nextOwnProps, todos, addTodo }
    ownProps = nextOwnProps
    if (!shallowEqual(result, nextResult)) result = nextResult
    return result
  }
}
export default connectAdvanced(selectorFactory)(TodoApp)

解析

connectAdvanced接受两个参数

selectorFactory

第一个参数是一个工厂函数(高阶函数),它会在组件实例化的时候调用,它的返回值是一个selector用于根据新的state和props计算传递给包裹的组件的新props,所以它的返回值是一个属性对象。

默认值

值得研究的是selectorFactory的默认值,因为我们一般都很少会自定义connectAdvanced方法,而且这也是connect方法内部运行机制的重要部分。 以下就是selectorFactory的默认方法,很容易看懂,很多都是我们之前讲过的内容,比如initMapStateToProps, initMapDispatchToProps, initMergeProps这三个方法做了什么可以对应去看之前讲mapStateToProps, mapDispatchToProps, mergeProps这三篇文章,最终这个方法会根据pure的值返回两种selector,ture返回pureFinalPropsSelectorFactory,false返回impureFinalPropsSelectorFactory

// If pure is true, the selector returned by selectorFactory will memoize its results,
// allowing connectAdvanced's shouldComponentUpdate to return false if final
// props have not changed. If false, the selector will always return a new
// object and shouldComponentUpdate will always return true.
// 如果pure参数为真,selectorFactory返回的选择器将会记住它的结果,
// 允许connectAdvanced的shouldComponentUpdate返回false如果final
// props没有改变。如果为false,选择器将始终返回一个新
// 对象和shouldComponentUpdate将始终返回true。
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') {
    // 校验三个方法是否存在,并判断是否有dependsOnOwnProps属性
    verifySubselectors(
      mapStateToProps,
      mapDispatchToProps,
      mergeProps,
      options.displayName
    )
  }

  const selectorFactory = options.pure
    ? pureFinalPropsSelectorFactory
    : impureFinalPropsSelectorFactory

  // pure为true返回 function pureFinalPropsSelector
  // pure为false返回 function impureFinalPropsSelector
  return selectorFactory(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    dispatch,
    options
  )
}

--------- from selectorFactory.js

pureFinalPropsSelectorFactory

pure为true时返回pureFinalPropsSelectorFactory的执行结果。 1.pureFinalPropsSelector pureFinalPropsSelectorFactory又返回一个叫做pureFinalPropsSelector的方法,内部根据变量hasRunAtLeastOnce返回两个方法,hasRunAtLeastOnce见名知意,就是表示是否已经执行过一遍方法了。

return function pureFinalPropsSelector(nextState, nextOwnProps) {
  return hasRunAtLeastOnce
    ? handleSubsequentCalls(nextState, nextOwnProps)
    : handleFirstCall(nextState, nextOwnProps)
}

--------- from selectorFactory.js

2.handleFirstCall 如果第一次调用方法,则执行handleFirstCall,这个方法不难,主要就是计算出一些初始值,并赋值给变量,这些值的含义你们一看就知道是什么意思了。这些变量属于pureFinalPropsSelectorFactory方法内部的变量,所以这块形成了一个闭包,因为之后后续调用还要用到之前计算出来的值。

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
}

--------- from selectorFactory.js

3.handleSubsequentCalls 当第二次调用pureFinalPropsSelectorFactory就会执行handleSubsequentCalls方法,这个方法是不是很眼熟,因为我们讲areOwnPropsEqualareStatesEqual,我们详细讲过这个方法做了什么,这里我就总结一下,这个方法就是去根据areOwnPropsEqualareStatesEqual去计算新旧state和props是否变化,如果变化了就分别调用handleNewPropsAndNewStatehandleNewPropshandleNewState去计算新的state和props,并返回合并之后的props也就是mergedProps,如果没有变化则返回之前计算的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
}

--------- from selectorFactory.js

impureFinalPropsSelectorFactory

如果pure为false,则调用impureFinalPropsSelectorFactory方法,这个方法不会去区分第一次调用还是第二次调用,每次都会计算新的state和props,并返回合并之后的props,这也可以看出pure参数的作用,就是会去缓存之前计算的值,对新旧的值就行比对,避免不必要的计算和重新渲染以提升性能。

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

--------- from selectorFactory.js

connectOptions

第二个参数是一些配置项,这些配置项比较简单,直接看我的代码注释就好了。

// options object:
{
  // the func used to compute this HOC's displayName from the wrapped component's displayName.
  // probably overridden by wrapper functions such as connect()
  // 这个方法用于从包装组件的displayName计算这个HOC的displayName。
  // 可能被诸如connect()之类的包装器函数覆盖
  getDisplayName = name => `ConnectAdvanced(${name})`,

  // shown in error messages
  // probably overridden by wrapper functions such as connect()
  // 显示在错误消息中
  // 可能被诸如connect()之类的包装器函数覆盖
  methodName = 'connectAdvanced',

  // REMOVED: if defined, the name of the property passed to the wrapped element indicating the number of
  // calls to render. useful for watching in react devtools for unnecessary re-renders.
  // 移除了: 如果定义了,则将属性的名称传递给包裹的元素表明调用render的次数。
  // 在react devtools中查看不必要的重新呈现非常有用。
  renderCountProp = undefined,

  // determines whether this HOC subscribes to store changes
  // 决定是否HOC监听store的改变
  shouldHandleStateChanges = true,

  // REMOVED: the key of props/context to get the store
  // 移除了: props/context的键去获取store
  storeKey = 'store',

  // REMOVED: expose the wrapped component via refs
  // 移除了: 通过refs公开包装的组件
  withRef = false,

  // use React's forwardRef to expose a ref of the wrapped component
  // 使用React的forwardRef公开包装组件的ref
  forwardRef = false,

  // the context consumer to use
  // 要使用的context对象
  context = ReactReduxContext,

  // additional options are passed through to the selectorFactory
  // 其他选项被传递到selectorFactory
  ...connectOptions
} = {}

---------- from connectAdvanced.js

返回值

讲完参数之后我们来看一下connectAdvanced的返回值,最终会根据forwardRef参数的值返回两种组件,这个我们在讲connect的options的那篇文章里面也讲到了它的作用,hoistStatics是一个第三方库hoist-non-react-statics,它的作用是将不是react的特定静态方法从子组件复制到父组件。

return function wrapWithConnect(WrappedComponent) {
  //......
  if (forwardRef) {
      const forwarded = React.forwardRef(function forwardConnectRef(
        props,
        ref
      ) {
        return <Connect {...props} forwardedRef={ref} />
      })

      forwarded.displayName = displayName
      forwarded.WrappedComponent = WrappedComponent
      // Copies non-react specific statics from a child component to a parent component
      // 将不是react的特定静态方法从子组件复制到父组件
      return hoistStatics(forwarded, WrappedComponent)
    }

    return hoistStatics(Connect, WrappedComponent)
}

---------- from connectAdvanced.js

ConnectFunction

我们注意到我们传进来的WrappedComponent最终会和一个叫Connect的组件合并,那这个Connect组件又是什么鬼呢?它其实就是一个函数组件ConnectFunction,最终根据shouldHandleStateChanges返回两种类型的组件,第一种就是单纯的WrappedComponent,第二种是将WrappedComponent包裹在ContextToUse.Provider内部并返回,这两者都会用useMemo这个React hook优化一下,最后还会用React.memo再包裹一下。

// 一个函数组件
function ConnectFunction(props) {
  //......
  const renderedWrappedComponent = useMemo(
    () => <WrappedComponent {...actualChildProps} ref={forwardedRef} />,
    [forwardedRef, WrappedComponent, actualChildProps]
  )

  // If React sees the exact same element reference as last time, it bails out of re-rendering
  // that child, same as if it was wrapped in React.memo() or returned false from shouldComponentUpdate.
  // 如果React看到与上次完全相同的元素引用,它将停止重新渲染这个子元素,
  // 就像它被包装在React.memo()中一样,或者从shouldComponentUpdate返回false。
  const renderedChild = useMemo(() => {
    if (shouldHandleStateChanges) {
      // If this component is subscribed to store updates, we need to pass its own
      // subscription instance down to our descendants. That means rendering the same
      // Context instance, and putting a different value into the context.
      // 如果这个组件订阅了存储更新,我们需要传递它自己的subscription实例传递到它的后代。
      // 这意味着渲染相同上下文实例,并将不同的值放入上下文。
      return (
        <ContextToUse.Provider value={overriddenContextValue}>
          {renderedWrappedComponent}
        </ContextToUse.Provider>
      )
    }

    return renderedWrappedComponent
  }, [ContextToUse, renderedWrappedComponent, overriddenContextValue])

  return renderedChild
}

// If we're in "pure" mode, ensure our wrapper component only re-renders when incoming props have changed.
// 如果我们处于“pure”模式,确保包装器组件只在传入的props发生更改时重新呈现。
const Connect = pure ? React.memo(ConnectFunction) : ConnectFunction

---------- from connectAdvanced.js

ConnectFunction中的其他逻辑

除了返回值外ConnectFunction内部还有很多逻辑,内容比较琐碎,所以我建议你去看我注释过的源码src/components/connectAdvanced.js。 这里主要说几点: 1.内部用了很多React hooks,比如用多次用useMemo去进行优化。 2.之前说的selectorFactory就是在这里调用的,计算出来的props就是actualChildProps传递给了renderedWrappedComponent。 3.通过useReducer实现了当state变化时强制组件更新。 4.通过useRef保存可变的数据,因为函数组件里面没有实例对象,所以它的作用类似于创建了一个实例属性。

最后更新时间: 7/4/2019, 8:54:14 AM