美文网首页
react-redux简介(二)源码解析

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

作者: 前端开发爱好者 | 来源:发表于2019-04-17 00:03 被阅读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。

    相关文章

      网友评论

          本文标题:react-redux简介(二)源码解析

          本文链接:https://www.haomeiwen.com/subject/urpwwqtx.html