react-redux简介(二)源码解析

2019-04-17  本文已影响0人  前端开发爱好者

写在开头

前置知识内容,reactreact-redux

git clone https://github.com/reduxjs/react-redux.git

本文对应redux版本为redux v6.0.1

一、结构

react-redux源码结构

在react-redux的src目录下可以清楚的看到react-redux有三个文件夹components(用于Provider组件),connect(用于暴露出来的connect函数),utils(JavaScript公共函数)。通过阅读index.js文件可以清楚的看到redux导出的5个核心方法

文件 功能
index redux的入口文件 用法
components/Context.js 提供核心APIcreateStore根据reducer,preState,applyMiddleware。创建并返回store
components/Provider.js 提供核心APIcombineReducers,用于合并拆分的reducer
components/connectAdvanced.js 提供核心APIbindActionCreators 可以简化dispatch action的调用方法。
connect/connect.js 提供核心APIconnect返回一个高阶组件,是对connectAdvanced的包装。
connect/mapDispatchToProps.js connectAdvanced()中被调用,使用了职责链模式处理connect中传入的mapDispatchToProps
connect/mapStateToProps.js connectAdvanced()中被调用,使用了职责链模式处理connect中传入的mapStateToProps
connect/mergeProps.js connectAdvanced()中被调用,使用了职责链模式处理connect中传入的mergeProps
connect/selectFactory.js 内置的action.type。
connect/verifySubselectors.js 数据校验。
connect/wrapMapToProps.js mapStateToProps,mapDispatchToProps中调用。
utils/isPlainObject.js 判断是否是简单对象。
utils/shallowEqual.js 判断是否是简单对象。
utils/verifyPlainObject.js 判断是否是简单对象。
utils/warning.js 用于输出警告信息。
utils/wrapActionCreators.js 用于输出警告信息。

二、工具文件

2.1 isPlainObject

源码截取

export default function isPlainObject(obj) {
  if (typeof obj !== 'object' || obj === null) return false

  let proto = obj
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto)
  }

  return Object.getPrototypeOf(obj) === proto
}

本函数与redux源码中的isPlainObject相同。函数用于判断一个对象是否是一个简单对象,简单对象是指直接使用对象字面量{}或者new Object()Object.create(null)所创建的对象。jQuerylodash等JavaScript函数均对此有所实现.redux的老期版本使用了ladash的实现版本,后改为自己实现。

2.2 shallowEqual.js

源码截取

const hasOwn = Object.prototype.hasOwnProperty

function is(x, y) {
// SameValue algorithm
  if (x === y) {
  // +0 != -0
    return x !== 0 || y !== 0 || 1 / x === 1 / y
  } else {
  //NaN == NaN
    return x !== x && y !== y
  }
}

export default function shallowEqual(objA, objB) {
  if (is(objA, objB)) return true

  if (
    typeof objA !== 'object' ||
    objA === null ||
    typeof objB !== 'object' ||
    objB === null
  ) {
    return false
  }

  const keysA = Object.keys(objA)
  const keysB = Object.keys(objB)

  if (keysA.length !== keysB.length) return false

  for (let i = 0; i < keysA.length; i++) {
    if (!hasOwn.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) {
      return false
    }
  }

  return true
}

函数使用了fbjs的实现方式,比较传入的值是否相等,比较传入的两个对象是否有相同的属性,属性值。

2.3 verifyPlainObject.js

源码截取:

import isPlainObject from './isPlainObject'
import warning from './warning'

export default function verifyPlainObject(value, displayName, methodName) {
  if (!isPlainObject(value)) {
    warning(
      `${methodName}() in ${displayName} must return a plain object. Instead received ${value}.`
    )
  }
}

用于校验传入的value参数是否为普通对象。对于非普通对象,调用warning方法进行报错

2.4 warning.js

源码截取:

export default function warning(message) {
  /* eslint-disable no-console */
  if (typeof console !== 'undefined' && typeof console.error === 'function') {
    console.error(message)
  }
  /* eslint-enable no-console */
  try {
    // This error was thrown as a convenience so that if you enable
    // "break on all exceptions" in your console,
    // it would pause the execution at this line.
    throw new Error(message)
    /* eslint-disable no-empty */
  } catch (e) {}
  /* eslint-enable no-empty */
}

此函数用于打印错误信息,抛出错误信息。与redux源码中的warning.js相同。使用throw new Error(message)是为了方便调试时中断执行

2.5 wrapActionCreators.js

源码截取:

import { bindActionCreators } from 'redux'

export default function wrapActionCreators(actionCreators) {
  return dispatch => bindActionCreators(actionCreators, dispatch)
}

函数接受actionCreators参数。返回一个函数,接受dispatch参数,返回已经使用bindActionCreators包装好,可以直接执行的actionCreators。

三、npm依赖

用于报错。react-is

包名称 作用
invariant 接受两个参数,如果第一个是false,那么将抛出错误信息,错误信息的内容是第二个参数
react-is 检验react元素的类型
prop-types 用于prop-type的检验
loose-envify

四、核心文件

3.1 components/connectAdvanced.js

此文件暴露出的是一个connectAdvanced函数,是react-redux直接暴露出的四个方法/组件之一。但是似乎并不常用,在没有浏览源码时,我难以察觉此API的存在,这个函数是react-redux的核心,此文件的逻辑依赖其他文件。请在最后在阅读此部分内容。
首先从头开始分析,函数传入的参数如下。

function connectAdvanced(
  selectorFactory,
  // 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()
    getDisplayName = name => `ConnectAdvanced(${name})`,

    // shown in error messages
    // probably overridden by wrapper functions such as 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.
    renderCountProp = undefined,

    // determines whether this HOC subscribes to store changes
    shouldHandleStateChanges = true,

    // REMOVED: the key of props/context to get the store
    storeKey = 'store',

    // REMOVED: expose the wrapped component via refs
    withRef = false,

    // use React's forwardRef to expose a ref of the wrapped component
    forwardRef = false,

    // the context consumer to use
    context = ReactReduxContext,

    // additional options are passed through to the selectorFactory
    ...connectOptions
  } = {}
) {/**code**/}

函数执行中,首先使用错误断言(第一个参数为false。就将第二个参数作为错误内容抛出)插件invariant进行参数校验。校验参数,规则为renderCountProp必须为undefined,!withRef必须为true,storeKey必须为"store"。源码如下。

invariant(
  renderCountProp === undefined,
  `renderCountProp is removed. render counting is built into the latest React dev tools profiling extension`
)

invariant(
  !withRef,
  'withRef is removed. To access the wrapped instance, use a ref on the connected component'
)

invariant(
  storeKey === 'store',
  'storeKey has been removed and does not do anything. ' +
  'To use a custom Redux store for specific components, create a custom React context with ' +
  'React.createContext(), and pass the context object to React Redux\'s Provider and specific components' +
  ' like: <Provider context={MyContext}><ConnectedComponent context={MyContext} /></Provider>. ' +
  'You may also pass a {context : MyContext} option to connect'
)

然后创建一个变量存储 Context。默认值是从ReactContext中取得的。
在使用connect时,就是对此函数的调用。最终返回的正是connectAdvanced函数所返回的wrapWithConnect函数。
下面来分析一下这个wrapWithConnect函数。这个函数接受一个React组件作为参数。函数内部首先使用react-is插件和invariant插件对函数的参数进行了校验。 如果是非正式环境直接抛出错误。 接下来获取传入组件的 displayName,主要用于报错。

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

const displayName = getDisplayName(wrappedComponentName)

接下来

const selectorFactoryOptions = {
      ...connectOptions,
      getDisplayName,   用处不大。用来表示connectAdvanced组件和包含的组件的关系。默认为name=>'ConnectAdvanced(' + name + ')',如果使用connect的话。这个参数会被覆盖为connect(name)。主要用于在selectorFactory中验证抛出warning时使用,会被加入到connectOptions一起传给selectorFactorFactory。
      methodName, 当前的名称,默认是`connectAdvanced`,如果使用`connect()`函数,会被覆盖成`connect`
      renderCountProp,
      shouldHandleStateChanges,
      storeKey,
      displayName,
      wrappedComponentName,
      WrappedComponent
    }

    const { pure } = connectOptions

    let OuterBaseComponent = Component

    if (pure) {
      OuterBaseComponent = PureComponent
    }

定义一个参数对象selectorFactoryOptions,并且根据传入的connectOptions.pure参数来确定返回的高阶组件是Component还是PureComponent
接下来

function makeDerivedPropsSelector() {
  let lastProps
  let lastState
  let lastDerivedProps
  let lastStore
  let lastSelectorFactoryOptions
  let sourceSelector

  return function selectDerivedProps(
    state,
    props,
    store,
    selectorFactoryOptions
  ) {
    if (pure && lastProps === props && lastState === state) {
      return lastDerivedProps
    }

    if (
      store !== lastStore ||
      lastSelectorFactoryOptions !== selectorFactoryOptions
    ) {
      lastStore = store
      lastSelectorFactoryOptions = selectorFactoryOptions
      sourceSelector = selectorFactory(
        store.dispatch,
        selectorFactoryOptions
      )
    }

    lastProps = props
    lastState = state

    const nextProps = sourceSelector(state, props)

    lastDerivedProps = nextProps
    return lastDerivedProps
  }
}

定义makeDerivedPropsSelector函数。函数通过闭包存储state,props,store,selectorFactoryOptions这四个变量来实现每次这四个变更更新后与之前值的对比。
接下来

function makeChildElementSelector() {
  let lastChildProps, lastForwardRef, lastChildElement, lastComponent
  return function selectChildElement(
    WrappedComponent,
    childProps,
    forwardRef
  ) {
    if (
      childProps !== lastChildProps ||
      forwardRef !== lastForwardRef ||
      lastComponent !== WrappedComponent
    ) {
      lastChildProps = childProps
      lastForwardRef = forwardRef
      lastComponent = WrappedComponent
      lastChildElement = (
        <WrappedComponent {...childProps} ref={forwardRef}/>
      )
    }
    return lastChildElement
  }
}

紧接着又定义了一个makeChildElementSelector函数,具体功能与上一个函数相似。
接下来,定义了React组件。

class Connect extends OuterBaseComponent {
  constructor(props) {
    super(props)
    // 断言 forwardRef ? !props.wrapperProps[storeKey] : !props[storeKey]。检测是否存在redux store,已被移除则抛出错误。
    this.selectDerivedProps = makeDerivedPropsSelector()
    this.selectChildElement = makeChildElementSelector()
    this.indirectRenderWrappedComponent = this.indirectRenderWrappedComponent.bind(this)
  }

  indirectRenderWrappedComponent(value) {
    // calling renderWrappedComponent on prototype from indirectRenderWrappedComponent bound to `this`
    return this.renderWrappedComponent(value)
  }

  renderWrappedComponent(value) {
    invariant(
      value,
      `Could not find "store" in the context of ` +
      `"${displayName}". Either wrap the root component in a <Provider>, ` +
      `or pass a custom React context provider to <Provider> and the corresponding ` +
      `React context consumer to ${displayName} in connect options.`
    )
    // 断言value,提示无法在上下文中找到store。可以将根组件包裹在Provider内,或者将一个自动你故意的contextProvider传入Provider。
    const { storeState, store } = value
    let wrapperProps = this.props
    let forwardedRef

    if (forwardRef) {
      wrapperProps = this.props.wrapperProps
      forwardedRef = this.props.forwardedRef
    }

    let derivedProps = this.selectDerivedProps(
      storeState,
      wrapperProps,
      store,
      selectorFactoryOptions
    )

    return this.selectChildElement(
      WrappedComponent,
      derivedProps,
      forwardedRef
    )
  }

  render() {
    const ContextToUse =
      this.props.context &&
      this.props.context.Consumer &&
      isContextConsumer(<this.props.context.Consumer/>)
        ? this.props.context
        : Context

    return (
      <ContextToUse.Consumer>
        {this.indirectRenderWrappedComponent}
      </ContextToUse.Consumer>
    )
  }
}

组件内使用了之前的两个函数已确保只有props改变了才会更新组件。在render内可以看到,Consumer也可以通过props直接传入,而不适用Provider。

Connect.WrappedComponent = WrappedComponent
Connect.displayName = displayName  //包装显示名字以利于调试

if (forwardRef) {
  // 使用React.forwardRef将refs显式的转发到内部
  const forwarded = React.forwardRef(function forwardConnectRef(props, ref) {
    return <Connect wrapperProps={props} forwardedRef={ref}/>
  })

  forwarded.displayName = displayName
  forwarded.WrappedComponent = WrappedComponent
  // 拷贝WrappedComponent的静态方法
  return hoistStatics(forwarded, WrappedComponent)
}
return hoistStatics(Connect, WrappedComponent)

返回被connect包装好的组件。

校验完成后

3.2 components/Context.js

源码截取

import React from 'react'
export const ReactReduxContext = React.createContext(null)
export default ReactReduxContext

此函数主要创建一个React Context。用于存储store。以便于可以在被connect包装额组件中会获取context。

3.3 components/Provider.js

Provider.js的源码非常简单,为一个公共的React组件,主要作用是,将storeS和storeState存储在ReactContext。在组件内部订阅了store变化。在每次action触发时,通过订阅函数的执行来设置当前组件的state,从而引起子组件的更新。下面是源代码

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { ReactReduxContext } from './Context'
class Provider extends Component {
  constructor(props) {
    super(props)
    const { store } = props
    this.state = {
      storeState: store.getState(),
      store
    }
  }
  componentDidMount() {
    this._isMounted = true
    this.subscribe()
  }

  componentWillUnmount() {
    if (this.unsubscribe) this.unsubscribe()
    this._isMounted = false
  }

  componentDidUpdate(prevProps) {
    if (this.props.store !== prevProps.store) {
      //store变化,重新调用订阅函数。如果之前订阅过就取消之前的订阅
      if (this.unsubscribe) this.unsubscribe()
      this.subscribe()
    }
  }

  subscribe() {
    const { store } = this.props
    this.unsubscribe = store.subscribe(() => {
      const newStoreState = store.getState()
      // 组件没有挂载,直接返回
      if (!this._isMounted) {
        return
      }
      this.setState(providerState => {
        // If the value is the same, skip the unnecessary state update.
        if (providerState.storeState === newStoreState) {
          return null
        }

        return { storeState: newStoreState }
      })
    })
    // Actions might have been dispatched between render and mount - handle those
    const postMountStoreState = store.getState()
    if (postMountStoreState !== this.state.storeState) {
      this.setState({ storeState: postMountStoreState })
    }
  }

  render() {
    const Context = this.props.context || ReactReduxContext
    return (
      <Context.Provider value={this.state}>
        {this.props.children}
      </Context.Provider>
    )
  }
}
Provider.propTypes = {
  store: PropTypes.shape({
    subscribe: PropTypes.func.isRequired,
    dispatch: PropTypes.func.isRequired,
    getState: PropTypes.func.isRequired
  }),
  context: PropTypes.object,
  children: PropTypes.any
}
export default Provider

3.4 connect/connect.js

connect.js文件最后expoet一个connect函数,这个就是react-redux直接暴露出来的connect函数,也是最常用的核心函数,主要用于将actionCreators和storeState传递给子组件。

源码截取

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
}

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, {
      methodName: 'connect',
      getDisplayName: name => `Connect(${name})`,
      shouldHandleStateChanges: Boolean(mapStateToProps),
      initMapStateToProps,
      initMapDispatchToProps,
      initMergeProps,
      pure,
      areStatesEqual,
      areOwnPropsEqual,
      areStatePropsEqual,
      areMergedPropsEqual,
      ...extraOptions
    })
  }
}

export default createConnect()

首先解释一下此文件的工具函数match。match函数,接受三个参数,arg:参数,factories:函数数组,name:用于报错洗脑洗。函数内部遍历函数数组,如果返回了结果,那么将这个结果返回,如果遍历结束仍没有结果,则抛出错误。
工具函数strictEqual主要用于传入的两个参数是否严格相等。
connect函数的代码结构也比较简单,首先定义了一个高阶函数createConnect,将createConnect()暴露出来。其中crateConnect函数主要接受五个参数,分别是核心函数connectHOC,四个默认值:mapStateToPropsFactories,mapDispatchToPropsFactories,mergePropsFactories,selectorFactory。返回的的函数就是我们使用的connect函数,函数主要接受四个可选参数,具体如下。

function connect(
    mapStateToProps,    //用于将store的state的数据结构映射到传入组件的props属性
    mapDispatchToProps,     //用于将dispatch传入子组件的props属性。
    mergeProps,     // 参数传入前两个参数处理后的props和当前的props,返回一个新的对象,这个新的对象会作为props传入被包装的组件。应用根据props,筛选state数据。把 props 中的某个特定变量与 action creator 绑定在一起。参数默认为Object.assign({}, ownProps, stateProps, dispatchProps)
    {
      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')

使用match函数和相应的Factories函数对传入的参数做处理。主要作用是根据传入connect参数的函数的类型,对其进行处理,比如说传入的是函数,传入的是对象,或者说传入的是undefined(不传)。
然后返回connectHOC(connectAdvanced所暴露出的函数)函数的执行结果,connectHOC恰好是返回一个高阶函数wrapWithConnect(WrappedComponent)。这个函数就是我们在日常使用中connect()返回的函数。

3.5 connect/mapDispatchToProps.js

源码截取

import { bindActionCreators } from 'redux'
import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps'

export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) {
  return typeof mapDispatchToProps === 'function'
    ? wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps')
    : undefined
}

export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {
  return !mapDispatchToProps
    ? wrapMapToPropsConstant(dispatch => ({ dispatch }))
    : undefined
}

export function whenMapDispatchToPropsIsObject(mapDispatchToProps) {
  return mapDispatchToProps && typeof mapDispatchToProps === 'object'
    ? wrapMapToPropsConstant(dispatch =>
        bindActionCreators(mapDispatchToProps, dispatch)
      )
    : undefined
}

export default [
  whenMapDispatchToPropsIsFunction,
  whenMapDispatchToPropsIsMissing,
  whenMapDispatchToPropsIsObject
]

此文件暴露出一个函数组成的数组,每个函数接受一个参数(connect函数的第二个参数)。根据参数的类型对参数做处理,返回一个统一的函数。

3.6 connect/mapStateToProps.js

源码截取

import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps'

export function whenMapStateToPropsIsFunction(mapStateToProps) {
  return typeof mapStateToProps === 'function'
    ? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
    : undefined
}

export function whenMapStateToPropsIsMissing(mapStateToProps) {
  return !mapStateToProps ? wrapMapToPropsConstant(() => ({})) : undefined
}

export default [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing]

此函数与上一个函数作用相同,主要是针对connect函数传入的第一个参数(mapStateToProps)的类型做处理,类型可以是函数,或者是不传。

3.7 connect/mergeProps.js

import verifyPlainObject from '../utils/verifyPlainObject'

export function defaultMergeProps(stateProps, dispatchProps, ownProps) {
  return { ...ownProps, ...stateProps, ...dispatchProps }
}

export function wrapMergePropsFunc(mergeProps) {
  return function initMergePropsProxy(
    dispatch,
    { displayName, pure, areMergedPropsEqual }
  ) {
    let hasRunOnce = false
    let mergedProps

    return function mergePropsProxy(stateProps, dispatchProps, ownProps) {
      const nextMergedProps = mergeProps(stateProps, dispatchProps, ownProps)

      if (hasRunOnce) {
        if (!pure || !areMergedPropsEqual(nextMergedProps, mergedProps))
          mergedProps = nextMergedProps
      } else {
        hasRunOnce = true
        mergedProps = nextMergedProps

        if (process.env.NODE_ENV !== 'production')
          verifyPlainObject(mergedProps, displayName, 'mergeProps')
      }

      return mergedProps
    }
  }
}

export function whenMergePropsIsFunction(mergeProps) {
  return typeof mergeProps === 'function'
    ? wrapMergePropsFunc(mergeProps)
    : undefined
}

export function whenMergePropsIsOmitted(mergeProps) {
  return !mergeProps ? () => defaultMergeProps : undefined
}

export default [whenMergePropsIsFunction, whenMergePropsIsOmitted]

此文件主要暴露了四个函数,和一个函数数组。但其中主要的只有两个函数。
defaultMergeProps:函数传入了stateProps,dispatchProps,ownProps三个参数。函数使用es6的对象结构,直接返回了一个新的对象。
wrapMergePropsFunc:由函数名可以理解此函数的作用是处理mergeProps,为函数时的情况。此函数直接返回一个新的函数initMergePropsProxy,参数为( dispatch, { displayName, pure, areMergedPropsEqual}),在函数内部定义了两个变量hasRunOnce,mergedProps(用于缓存,性能优化)。然后直接返回了一个新的函数mergePropsPropsProxy,此函数接受了三个参数(stateProps, dispatchProps, ownProps)。函数首先使用mergeProps函数取得了合并后的结果为nextMergeProps。然后判断是否为第一次执行。不是的话,直接将刚才计算好的nextMergedProps存入变量mergedProps,并将结果返回。在第二次执行此函数时,会进行如下判断

 if (!pure || !areMergedPropsEqual(nextMergedProps, mergedProps))
          mergedProps = nextMergedProps

whenMergePropsIsFunction:包装了一下wrapMergePropsFunc函数
whenMergePropsIsOmitted:包装了defaultMergeProps函数。
最后直接暴露出一个数组[whenMergePropsIsFunction, whenMergePropsIsOmitted]

3.8 connect/selectFactory.js

源码解析

import verifySubselectors from './verifySubselectors'

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

export function pureFinalPropsSelectorFactory(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
  dispatch,
  { areStatesEqual, areOwnPropsEqual, areStatePropsEqual }
) {
  let hasRunAtLeastOnce = false
  let state
  let ownProps
  let stateProps
  let dispatchProps
  let mergedProps

  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
  }

  function handleNewPropsAndNewState() {
    stateProps = mapStateToProps(state, ownProps)

    if (mapDispatchToProps.dependsOnOwnProps)
      dispatchProps = mapDispatchToProps(dispatch, ownProps)

    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
    return mergedProps
  }

  function handleNewProps() {
    if (mapStateToProps.dependsOnOwnProps)
      stateProps = mapStateToProps(state, ownProps)

    if (mapDispatchToProps.dependsOnOwnProps)
      dispatchProps = mapDispatchToProps(dispatch, ownProps)

    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
    return mergedProps
  }

  function handleNewState() {
    const nextStateProps = mapStateToProps(state, ownProps)
    const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps)
    stateProps = nextStateProps

    if (statePropsChanged)
      mergedProps = mergeProps(stateProps, dispatchProps, ownProps)

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

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

// TODO: Add more comments

// 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.

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') {
    verifySubselectors(
      mapStateToProps,
      mapDispatchToProps,
      mergeProps,
      options.displayName
    )
  }

  const selectorFactory = options.pure
    ? pureFinalPropsSelectorFactory
    : impureFinalPropsSelectorFactory

  return selectorFactory(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    dispatch,
    options
  )
}

此函数主要是用在connectAdvanced函数中被调用。
此模块返回的函数为

(state, ownProps)=>final props

此函数主要是对props进行差异比较,避免不必要的组件更新。从而提高性能。如果内部组件需要。除props和store歪的其他状态,则pure必为false。
option.areStatesEqual默认值为(===),areOwnPropsEqual(默认值为shallowEqual),areStatePropsEqual(默认值为shallowEqual),areMergedPropsEqual(默认值为shallowEqual)

3.9 connect/verifySubSelector.js

部分源码

import warning from '../utils/warning'

function verify(selector, methodName, displayName) {
  if (!selector) {
    throw new Error(`Unexpected value for ${methodName} in ${displayName}.`)
  } else if (
    methodName === 'mapStateToProps' ||
    methodName === 'mapDispatchToProps'
  ) {
    if (!selector.hasOwnProperty('dependsOnOwnProps')) {
      warning(
        `The selector for ${methodName} of ${displayName} did not specify a value for dependsOnOwnProps.`
      )
    }
  }
}

export default function verifySubselectors(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
  displayName
) {
  verify(mapStateToProps, 'mapStateToProps', displayName)
  verify(mapDispatchToProps, 'mapDispatchToProps', displayName)
  verify(mergeProps, 'mergeProps', displayName)
}

此函数传入四个参数,mapStateToProps,mapDispatchToProps,mergeProps,displayName,主要判断前三个参数是否为false(隐式转换的结果是false),如有则直接抛出错误信息。对于mapStateToProps,mapDispatchToProps,如果没有dependsOnOwnProps属性则抛出错误。

3.10 connect/wrapMapToProps.js

源码截取

import verifyPlainObject from '../utils/verifyPlainObject'

export function wrapMapToPropsConstant(getConstant) {
  return function initConstantSelector(dispatch, options) {
    const constant = getConstant(dispatch, options)
    function constantSelector() {
      return constant
    }
    constantSelector.dependsOnOwnProps = false
    return constantSelector
  }
}

export function getDependsOnOwnProps(mapToProps) {
  return mapToProps.dependsOnOwnProps !== null &&
  mapToProps.dependsOnOwnProps !== undefined
    ? Boolean(mapToProps.dependsOnOwnProps)
    : mapToProps.length !== 1
}

export function wrapMapToPropsFunc(mapToProps, methodName) {
  return function initProxySelector(dispatch, { displayName }) {
    const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
      return proxy.dependsOnOwnProps
        ? proxy.mapToProps(stateOrDispatch, ownProps)
        : proxy.mapToProps(stateOrDispatch)
    }
    proxy.dependsOnOwnProps = true
    proxy.mapToProps = function detectFactoryAndVerify(stateOrDispatch, ownProps) {
      proxy.mapToProps = mapToProps
      proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)
      let props = proxy(stateOrDispatch, ownProps)
      if (typeof props === 'function') {
        proxy.mapToProps = props
        proxy.dependsOnOwnProps = getDependsOnOwnProps(props)
        props = proxy(stateOrDispatch, ownProps)
      }
      if (process.env.NODE_ENV !== 'production')
        verifyPlainObject(props, displayName, methodName)
      return props
    }
    return proxy
  }
}

这个函数暴露出了三个函数wrapMapToPropsConstant,getDependsOnOwnProps,wrapMapToPropsFunc。暴露出的这几个函数主要在mapStateToProps,mapDispatchToprosp中被调用。

首先来说第一个函数wrapMapToPropsConstant,找出其使用场景如下。

// 第一种调用
export function whenMapStateToPropsIsMissing(mapStateToProps) {
  return !mapStateToProps ? wrapMapToPropsConstant(() => ({})) : undefined
}
// 第二种调用
export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {
  return !mapDispatchToProps
    ? wrapMapToPropsConstant(dispatch => ({ dispatch }))
    : undefined
}

// 第三种调用
export function whenMapDispatchToPropsIsObject(mapDispatchToProps) {
  return mapDispatchToProps && typeof mapDispatchToProps === 'object'
    ? wrapMapToPropsConstant(dispatch =>
        bindActionCreators(mapDispatchToProps, dispatch)
      )
    : undefined
}

函数主体返回了一个新的函数initConstantSelector,新函数接受两个参数dispatch和options,根据调用地方的代码

// 第一种
wrapMapToPropsConstant(() => ({}))的结果为
function initConstantSelector(dispatch, options) {
    function constantSelector() {
      return {}
    }
    constantSelector.dependsOnOwnProps = false
    return constantSelector
}

// 第二种
wrapMapToPropsConstant(dispatch => ({ dispatch }))的结果为
function initConstantSelector(dispatch, options) {
    function constantSelector() {
      return {dispatch:dispatch}
    }
    constantSelector.dependsOnOwnProps = false
    return constantSelector
}

// 第三种
wrapMapToPropsConstant(dispatch =>
        bindActionCreators(mapDispatchToProps, dispatch))的结果为
function initConstantSelector(dispatch, options) {
    function constantSelector() {
      return {dispatch:bindActionCreators(mapDispatchToProps, dispatch)}
    }
    constantSelector.dependsOnOwnProps = false
    return constantSelector
}

其中mapStateToProps会被作为connectAdvanced函数的第二个参数的initMapStateToProps属性
其中mapStateToProps会被作为initMapDispatchToProps函数的第二个参数的initMapDispatchToProps属性

文件暴露出來的第二个参数wrapMapToPropsFunc。此函数返回了一个新的initProxySelector函数,函数接受两个参数(dispatch,{displayName})。找出调用此函数的地方。

// 第一种
export function whenMapStateToPropsIsFunction(mapStateToProps) {
  return typeof mapStateToProps === 'function'
    ? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
    : undefined
}

// 第二种
export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) {
  return typeof mapDispatchToProps === 'function'
    ? wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps')
    : undefined
}

对于第一种来说

wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
调用结果

function initProxySelector(dispatch, { displayName }) {
  const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
    return proxy.dependsOnOwnProps
      ? proxy.mapToProps(stateOrDispatch, ownProps)
      : proxy.mapToProps(stateOrDispatch)
  }

  // allow detectFactoryAndVerify to get ownProps
  proxy.dependsOnOwnProps = true

  proxy.mapToProps = function detectFactoryAndVerify(
    stateOrDispatch,
    ownProps
  ) {
    proxy.mapToProps = mapStateToProps
    proxy.dependsOnOwnProps = getDependsOnOwnProps(mapStateToProps)
    let props = proxy(stateOrDispatch, ownProps)

    if (typeof props === 'function') {
      proxy.mapToProps = props
      proxy.dependsOnOwnProps = getDependsOnOwnProps(props)
      props = proxy(stateOrDispatch, ownProps)
    }

    // 不是生产环境,校验props是否为简单对象。
    if (process.env.NODE_ENV !== 'production')
      verifyPlainObject(props, displayName, 'mapStateToProps')
    return props
  }
  return proxy
}

其中initProxySelector会被作为connectAdvanced函数的第二个参数的initMapStateToProps属性。
其中initProxySelector会被作为initMapDispatchToProps函数的第二个参数的initMapDispatchToProps属性。

五、总结

以上这些内容就是我对于react-redux的部分理解。
react-redux为我们在react中使用redux进行状态管理提供了方便。react-redux主要暴露出了一个connect函数和Provide组件。,我们只需要去定义connect的函数就可以了,大大的降低了使用redux的成本。

六、扩展链接

七、写在最后

从零开始学习react-redux十余天,总体感觉react-redux是非常优美的一个框架。不过由于个人的能力原因,对于源码的某些部分,比较难以理解,源码中告诫函数使用的较多,函数式编程思想很彻底。还有用于处理connect参数的职责链模式,react告诫组件,ref等知识。
文章未完善,将持续修改。

预告下一篇不定时更新文章:react的高阶组件和ref。

上一篇下一篇

猜你喜欢

热点阅读