react-redux之connect方法解析

connect简介

前方高能预警,有耐心才能看完文章!!

react-redux仅有2个API,Provider和connect,Provider提供的是一个顶层容器的作用,实现store的上下文传递。

connect方法比较复杂,虽然代码只有368行,但是为redux中常用的功能实现了和react连接的建立。

一个基础的connect方法如下:

connect(mapStatetoProps,mapdispatchToProps,mergeProps,options = {})

为什么我们需要react-redux

熟悉redux的人可能知道,redux是数据存储和管理的工具,但是想要在react中使用redux,并不能直接将store、action和react组件建立连接,所以就需要react-redux来结合react和redux

react-redux文件体积非常小,你完全不需要担心给你的项目带来太多的垃圾代码

从何处开始解析react-redux源码?

1、在JavaScript中,读懂别人的代码文件,你首先应该看的是函数的入口。

2、找到函数入口,然后看有哪些参数。

3、看看导入了哪些额外的插件,每个插件的作用大概预测一下。

4、进入函数体进行解读。在react插件中解读函数一个好处,就是react插件大部分都是采用了react组件的写法,你可以在react插件中看到很多react组件的影子。而不是像jQuery那样到处都是扩展性的方法,每个方法都有自己的设计模式,没有统一的规律可循。

react-redux使用场景

下面这个官方例子展示了mapStatetoProps和mapdispatchToProps的使用方法

import * as todoActionCreators from './todoActionCreators'
import * as counteractionCreators from './counteractionCreators'
import { bindActionCreators } from 'redux'

function mapStatetoProps(state) {
  return { todos: state.todos }
}

function mapdispatchToProps(dispatch) {
  return {
    todoActions: bindActionCreators(todoActionCreators,dispatch),counteractions: bindActionCreators(counteractionCreators,dispatch)
  }
}

export default connect(mapStatetoProps,mapdispatchToProps)(TodoApp)

mergeProps的用法

import * as actionCreators from './actionCreators'

function mapStatetoProps(state) {
  return { todos: state.todos }
}

function mergeProps(stateProps,dispatchProps,ownProps) {
  return Object.assign({},ownProps,{
    todos: stateProps.todos[ownProps.userId],addTodo: (text) => dispatchProps.addTodo(ownProps.userId,text)
  })
}

export default connect(mapStatetoProps,actionCreators,mergeProps)(TodoApp)

connect源码解析

源码有点长,你可以选择性的查看:

import { Component,createElement } from 'react'
import storeShape from '../utils/storeShape'
import shallowEqual from '../utils/shallowEqual'
import wrapActionCreators from '../utils/wrapActionCreators'
import warning from '../utils/warning'
import isPlainObject from 'lodash/isPlainObject'
import hoistStatics from 'hoist-non-react-statics'
import invariant from 'invariant'

const defaultMapStatetoProps = state => ({}) // eslint-disable-line no-unused-vars
const defaultMapdispatchToProps = dispatch => ({ dispatch })
const defaultMergeProps = (stateProps,parentProps) => ({
  ...parentProps,...stateProps,...dispatchProps
})

function getdisplayName(WrappedComponent) {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}

let errorObject = { value: null }
function tryCatch(fn,ctx) {
  try {
    return fn.apply(ctx)
  } catch (e) {
    errorObject.value = e
    return errorObject
  }
}

// Helps track hot reloading.
let nextVersion = 0

export default function connect(mapStatetoProps,options = {}) {
  const shouldSubscribe = Boolean(mapStatetoProps)
  const mapState = mapStatetoProps || defaultMapStatetoProps

  let mapdispatch
  if (typeof mapdispatchToProps === 'function') {
    mapdispatch = mapdispatchToProps
  } else if (!mapdispatchToProps) {
    mapdispatch = defaultMapdispatchToProps
  } else {
    mapdispatch = wrapActionCreators(mapdispatchToProps)
  }

  const finalMergeProps = mergeProps || defaultMergeProps
  const { pure = true,withRef = false } = options
  const checkMergedEquals = pure && finalMergeProps !== defaultMergeProps

  // Helps track hot reloading.
  const version = nextVersion++

  return function wrapWithConnect(WrappedComponent) {
    const connectdisplayName = `Connect(${getdisplayName(WrappedComponent)})`

    function checkStateShape(props,methodName) {
      if (!isPlainObject(props)) {
        warning(
          `${methodName}() in ${connectdisplayName} must return a plain object. ` +
          `Instead received ${props}.`
        )
      }
    }

    function computeMergedProps(stateProps,parentProps) {
      const mergedProps = finalMergeProps(stateProps,parentProps)
      if (process.env.NODE_ENV !== 'production') {
        checkStateShape(mergedProps,'mergeProps')
      }
      return mergedProps
    }

    class Connect extends Component {
      shouldComponentUpdate() {
        return !pure || this.haveOwnPropsChanged || this.hasstoreStateChanged
      }

      constructor(props,context) {
        super(props,context)
        this.version = version
        this.store = props.store || context.store

        invariant(this.store,`Could not find "store" in either the context or ` +
          `props of "${connectdisplayName}". ` +
          `Either wrap the root component in a <Provider>,` +
          `or explicitly pass "store" as a prop to "${connectdisplayName}".`
        )

        const storeState = this.store.getState()
        this.state = { storeState }
        this.clearCache()
      }

      computeStateProps(store,props) {
        if (!this.finalMapStatetoProps) {
          return this.configureFinalMapState(store,props)
        }

        const state = store.getState()
        const stateProps = this.doStatePropsDependONownProps ?
          this.finalMapStatetoProps(state,props) :
          this.finalMapStatetoProps(state)

        if (process.env.NODE_ENV !== 'production') {
          checkStateShape(stateProps,'mapStatetoProps')
        }
        return stateProps
      }

      configureFinalMapState(store,props) {
        const mappedState = mapState(store.getState(),props)
        const isFactory = typeof mappedState === 'function'

        this.finalMapStatetoProps = isFactory ? mappedState : mapState
        this.doStatePropsDependONownProps = this.finalMapStatetoProps.length !== 1

        if (isFactory) {
          return this.computeStateProps(store,props)
        }

        if (process.env.NODE_ENV !== 'production') {
          checkStateShape(mappedState,'mapStatetoProps')
        }
        return mappedState
      }

      computedispatchProps(store,props) {
        if (!this.finalMapdispatchToProps) {
          return this.configureFinalMapdispatch(store,props)
        }

        const { dispatch } = store
        const dispatchProps = this.dodispatchPropsDependONownProps ?
          this.finalMapdispatchToProps(dispatch,props) :
          this.finalMapdispatchToProps(dispatch)

        if (process.env.NODE_ENV !== 'production') {
          checkStateShape(dispatchProps,'mapdispatchToProps')
        }
        return dispatchProps
      }

      configureFinalMapdispatch(store,props) {
        const mappeddispatch = mapdispatch(store.dispatch,props)
        const isFactory = typeof mappeddispatch === 'function'

        this.finalMapdispatchToProps = isFactory ? mappeddispatch : mapdispatch
        this.dodispatchPropsDependONownProps = this.finalMapdispatchToProps.length !== 1

        if (isFactory) {
          return this.computedispatchProps(store,props)
        }

        if (process.env.NODE_ENV !== 'production') {
          checkStateShape(mappeddispatch,'mapdispatchToProps')
        }
        return mappeddispatch
      }

      updateStatePropsIfNeeded() {
        const nextStateProps = this.computeStateProps(this.store,this.props)
        if (this.stateProps && shallowEqual(nextStateProps,this.stateProps)) {
          return false
        }

        this.stateProps = nextStateProps
        return true
      }

      updatedispatchPropsIfNeeded() {
        const nextdispatchProps = this.computedispatchProps(this.store,this.props)
        if (this.dispatchProps && shallowEqual(nextdispatchProps,this.dispatchProps)) {
          return false
        }

        this.dispatchProps = nextdispatchProps
        return true
      }

      updateMergedPropsIfNeeded() {
        const nextMergedProps = computeMergedProps(this.stateProps,this.dispatchProps,this.props)
        if (this.mergedProps && checkMergedEquals && shallowEqual(nextMergedProps,this.mergedProps)) {
          return false
        }

        this.mergedProps = nextMergedProps
        return true
      }

      isSubscribed() {
        return typeof this.unsubscribe === 'function'
      }

      trySubscribe() {
        if (shouldSubscribe && !this.unsubscribe) {
          this.unsubscribe = this.store.subscribe(this.handleChange.bind(this))
          this.handleChange()
        }
      }

      tryUnsubscribe() {
        if (this.unsubscribe) {
          this.unsubscribe()
          this.unsubscribe = null
        }
      }

      componentDidMount() {
        this.trySubscribe()
      }

      componentwillReceiveProps(nextProps) {
        if (!pure || !shallowEqual(nextProps,this.props)) {
          this.haveOwnPropsChanged = true
        }
      }

      componentwillUnmount() {
        this.tryUnsubscribe()
        this.clearCache()
      }

      clearCache() {
        this.dispatchProps = null
        this.stateProps = null
        this.mergedProps = null
        this.haveOwnPropsChanged = true
        this.hasstoreStateChanged = true
        this.haveStatePropsBeenPrecalculated = false
        this.statePropsPrecalculationError = null
        this.renderedElement = null
        this.finalMapdispatchToProps = null
        this.finalMapStatetoProps = null
      }

      handleChange() {
        if (!this.unsubscribe) {
          return
        }

        const storeState = this.store.getState()
        const prevStoreState = this.state.storeState
        if (pure && prevStoreState === storeState) {
          return
        }

        if (pure && !this.doStatePropsDependONownProps) {
          const haveStatePropsChanged = tryCatch(this.updateStatePropsIfNeeded,this)
          if (!haveStatePropsChanged) {
            return
          }
          if (haveStatePropsChanged === errorObject) {
            this.statePropsPrecalculationError = errorObject.value
          }
          this.haveStatePropsBeenPrecalculated = true
        }

        this.hasstoreStateChanged = true
        this.setState({ storeState })
      }

      getWrappedInstance() {
        invariant(withRef,`To access the wrapped instance,you need to specify ` +
          `{ withRef: true } as the fourth argument of the connect() call.`
        )

        return this.refs.wrappedInstance
      }

      render() {
        const {
          haveOwnPropsChanged,hasstoreStateChanged,haveStatePropsBeenPrecalculated,statePropsPrecalculationError,renderedElement
        } = this

        this.haveOwnPropsChanged = false
        this.hasstoreStateChanged = false
        this.haveStatePropsBeenPrecalculated = false
        this.statePropsPrecalculationError = null

        if (statePropsPrecalculationError) {
          throw statePropsPrecalculationError
        }

        let shouldUpdateStateProps = true
        let shouldUpdatedispatchProps = true
        if (pure && renderedElement) {
          shouldUpdateStateProps = hasstoreStateChanged || (
            haveOwnPropsChanged && this.doStatePropsDependONownProps
          )
          shouldUpdatedispatchProps =
            haveOwnPropsChanged && this.dodispatchPropsDependONownProps
        }

        let haveStatePropsChanged = false
        let havedispatchPropsChanged = false
        if (haveStatePropsBeenPrecalculated) {
          haveStatePropsChanged = true
        } else if (shouldUpdateStateProps) {
          haveStatePropsChanged = this.updateStatePropsIfNeeded()
        }
        if (shouldUpdatedispatchProps) {
          havedispatchPropsChanged = this.updatedispatchPropsIfNeeded()
        }

        let haveMergedPropsChanged = true
        if (
          haveStatePropsChanged ||
          havedispatchPropsChanged ||
          haveOwnPropsChanged
        ) {
          haveMergedPropsChanged = this.updateMergedPropsIfNeeded()
        } else {
          haveMergedPropsChanged = false
        }

        if (!haveMergedPropsChanged && renderedElement) {
          return renderedElement
        }

        if (withRef) {
          this.renderedElement = createElement(WrappedComponent,{
            ...this.mergedProps,ref: 'wrappedInstance'
          })
        } else {
          this.renderedElement = createElement(WrappedComponent,this.mergedProps
          )
        }

        return this.renderedElement
      }
    }

    Connect.displayName = connectdisplayName
    Connect.WrappedComponent = WrappedComponent
    Connect.contextTypes = {
      store: storeShape
    }
    Connect.propTypes = {
      store: storeShape
    }

    if (process.env.NODE_ENV !== 'production') {
      Connect.prototype.componentwillUpdate = function componentwillUpdate() {
        if (this.version === version) {
          return
        }

        // We are hot reloading!
        this.version = version
        this.trySubscribe()
        this.clearCache()
      }
    }

    return hoistStatics(Connect,WrappedComponent)
  }
}

我们按照上面介绍的解析步骤来一步步有序的分析源码。

1、查看函数入口,以及需要传入的参数。

如果只是看这样一个函数体,我们无法得知每个参数到底是什么?有什么作用?但是,我们可以先结合使用的demo初步了解各个参数的作用。

export default function connect(mapStatetoProps,options = {}) {}

mapStatetoProps:传入所有state,返回指定的state数据。

function mapStatetoProps(state) {
      return { todos: state.todos }
    }

mapdispatchToProps:传入dispatch,返回使用bindActionCreators()绑定的action方法。我们不再这里讨论bindActionCreators的用法,这个知识将会放到redux解析的文章中。

function mapdispatchToProps(dispatch) {
  return bindActionCreators(Object.assign({},todoActionCreators,counteractionCreators),dispatch)
}

mergeProps:mergeProps如果不指定,则认返回 Object.assign({},stateProps,dispatchProps),顾名思义,mergeProps是合并的意思,将state合并后传递给组件。

function mergeProps(stateProps,text)
  })
}

options:通过配置项可以更加详细的定义connect的行为,通常只需要执行认值。

2、查看导入了哪些插件

import { Component,createElement } from 'react'
import storeShape from '../utils/storeShape'
import shallowEqual from '../utils/shallowEqual'
import wrapActionCreators from '../utils/wrapActionCreators'
import warning from '../utils/warning'
import isPlainObject from 'lodash/isPlainObject'
import hoistStatics from 'hoist-non-react-statics'
import invariant from 'invariant'

react:使用到了react组件,那么我们可以猜测connect和Provider类似,需要创建一个Connect组件。

storeShape:通过了redux常用API的类型验证。

import PropTypes from 'prop-types'
export default PropTypes.shape({
  subscribe: PropTypes.func.isrequired,dispatch: PropTypes.func.isrequired,getState: PropTypes.func.isrequired
})

shallowEqual:这个文件的作用是传入2个对象,首先比较对象是否一致,如果一致,则返回true,如果不一致,则获取2个对象的key数组,判断2个对象key数组的长度是否相等,如果不相等,返回false,如果相等,最后用for循环遍历A对象的key,如果当前的遍历值不存在于B的key中或者A对象的当前key的value不等于B对象的当前key的value,则返回false,如果不属于上面的任何情况,则返回true。(如果认为我这段讲的迷迷糊糊,你也可以自己理解下面的代码。)

export default function shallowEqual(objA,objB) {
  if (objA === objB) {
    return true
  }
  const keysA = Object.keys(objA)
  const keysB = Object.keys(objB)
  if (keysA.length !== keysB.length) {
    return false
  }
  // 测试A对象的key和B对象的key不一致
  const hasOwn = Object.prototype.hasOwnProperty
  for (let i = 0; i < keysA.length; i++) {
    if (!hasOwn.call(objB,keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) {
      return false
    }
  }
  return true
}

hasOwn的作用是判断对象里面是否包含某个属性。这段代码的实际用途是判断下一个props和当前的props是否一致。

shallowEqual(nextStateProps,this.stateProps)

wrapActionCreators:实现了bindActionCreators方法绑定action到组件的操作。

import { bindActionCreators } from 'redux'

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

函数使用方法

wrapActionCreators(mapdispatchToProps)

warning:在控制台打印warning信息

export default function warning(message) {
  if (typeof console !== 'undefined' && typeof console.error === 'function') {
    console.error(message)
  }
  try {
    throw new Error(message)
  } catch (e) {}
}

lodash/isPlainObject:检查传入的值是不是纯对象,如果是,返回true,否则返回false。方法详情查看 lodash之isPlainObject

function isPlainObject(value) {
  if (!isObjectLike(value) || baseGetTag(value) != objectTag) {
    return false;
  }
  var proto = getPrototype(value);
  if (proto === null) {
    return true;
  }
  var Ctor = hasOwnProperty.call(proto,'constructor') && proto.constructor;
  return typeof Ctor == 'function' && Ctor instanceof Ctor &&
    funcToString.call(Ctor) == objectCtorString;
}

hoist-non-react-statics:这段代码有点神奇,REACT_STATICS是一堆react的常用方法KNowN_STATICS函数的一些属性

var REACT_STATICS = {
    childContextTypes: true,contextTypes: true,defaultProps: true,displayName: true,getDefaultProps: true,mixins: true,propTypes: true,type: true
};
var KNowN_STATICS = {
    name: true,length: true,prototype: true,caller: true,arguments: true,arity: true
};
var isGetownPropertySymbolsAvailable = typeof Object.getownPropertySymbols === 'function';

module.exports = function hoistNonReactStatics(targetComponent,sourceComponent,customStatics) {
    if (typeof sourceComponent !== 'string') { // don't hoist over string (html) components
        var keys = Object.getownPropertyNames(sourceComponent);
        if (isGetownPropertySymbolsAvailable) {
            keys = keys.concat(Object.getownPropertySymbols(sourceComponent));
        }

        for (var i = 0; i < keys.length; ++i) {
            if (!REACT_STATICS[keys[i]] && !KNowN_STATICS[keys[i]] && (!customStatics || !customStatics[keys[i]])) {
                try {
                    targetComponent[keys[i]] = sourceComponent[keys[i]];
                } catch (error) {

                }
            }
        }
    }

    return targetComponent;
};

我们首先从函数入口解读,入口传入了3个参数,targetComponent,sourceComponent,customStatics,首先判断sourceComponent的类型不是一个字符串,然后使用getownPropertyNames获取sourceComponent对象的key,返回值是key组成的数组keys。接着判断isGetownPropertySymbolsAvailable(肯定是true),如果为true,执行下面的语句:

keys = keys.concat(Object.getownPropertySymbols(sourceComponent));

getownPropertySymbols和getownPropertyNames作用类似,但是getownPropertyNames只是返回字符串类型的key,而getownPropertySymbols可以返回Symbol类型的key。然后我们再把2种情况下的key拼接到一个数组里面返回新的keys。

后执行for语句,遍历keys,如果不包含REACT_STATICS中的react的静态方法,同时不包含KNowN_STATICS中的属性,同时不存在customStatics(传入函数的第三个参数不存在)或者存在但没有sourceComponent的key,就执行:

//将sourceComponent的方法写入targetComponent中
targetComponent[keys[i]] = sourceComponent[keys[i]];

最后返回targetComponent:

return targetComponent

方法在connect中的实际作用是:将WrappedComponent内的react静态方法绑定到Connect组件上。

hoistStatics(Connect,WrappedComponent)

invariant:我们看到invariant传入了好几个参数,第一个if语句表示如果不是生产环境,并且format没有定义,就抛出异常。第二个if表示如果condition未定义,同时format未定义,就抛出error,如果condition不存在但format存在,抛出另外的错误。(总结就是一个错误检查机制)

var NODE_ENV = process.env.NODE_ENV;

var invariant = function(condition,format,a,b,c,d,e,f) {
  if (NODE_ENV !== 'production') {
    if (format === undefined) {
      throw new Error('invariant requires an error message argument');
    }
  }

  if (!condition) {
    var error;
    if (format === undefined) {
      error = new Error(
        'Minified exception occurred; use the non-minified dev environment ' +
        'for the full error message and additional helpful warnings.'
      );
    } else {
      var args = [a,f];
      var argIndex = 0;
      error = new Error(
        format.replace(/%s/g,function() { return args[argIndex++]; })
      );
      error.name = 'Invariant Violation';
    }

    error.framesToPop = 1; // we don't care about invariant's own frame
    throw error;
  }

};

module.exports = invariant;

方法实际用途:检查store是否存在

invariant(this.store,` +
          `or explicitly pass "store" as a prop to "${connectdisplayName}".`
        )

3、定义几个参数认值常量

当你没有给组件绑定state和dispatch的时候,就执行认的配置。

defaultMapStatetoProps:传入state,返回空对象

defaultMapdispatchToProps: 传入dispatch,返回dispatch对象

defaultMergeProps:传入stateProps,parentProps,返回当前传入的对象。

const defaultMapStatetoProps = state => ({})
const defaultMapdispatchToProps = dispatch => ({ dispatch })
const defaultMergeProps = (stateProps,...dispatchProps
})

4、getdisplayName方法

返回当前传入的组件名

function getdisplayName(WrappedComponent) {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}

5、tryCatch方法
给fn函数指定上下文。

let errorObject = { value: null }
function tryCatch(fn,ctx) {
  try {
    return fn.apply(ctx)
  } catch (e) {
    errorObject.value = e
    return errorObject
  }
}

使用场景:在connect内调用tryCatch给updateStatePropsIfNeeded方法指定当前的上下文

tryCatch(this.updateStatePropsIfNeeded,this)

如果你不明白上面的代码,可以看下面比较简单的例子:

let b = {
  a: 1,e: function() {
    console.log(this.a)
  },c: function() {
    tryCatch(this.e,this)
  }
}

b.c() // 1

6、connect函数解析思路
connect函数是核心,我们需要大概了解函数做的事情,才能更好的读懂源码。
既然是函数,那就有返回值,connect()返回值是Connect组件(请注意大小写的区别)。

通俗点理解,使用connect可以把state和dispatch绑定到react组件,使得组件可以访问到redux的数据。
常看到下面这种写法:

export default connect(mapStatetoProps)(TodoApp)

我把connect的核心实现简化提取出来,是下面这种形式:WrappedComponent参数对应的就是TodoApp。函数最终返回的是将state和dispatch绑定到Connect之后的新组件。

funtion connect(mapStatetoProps) {
    return function wrapWithConnect(WrappedComponent) {
        class Connect extends Component {
        
        }
        return hoistStatics(Connect,WrappedComponent)
    }
}

7、Connect组件执行

既然已经知道connect函数返回的是Connect组件,而Connect组件继承于react,我们就可以按照react的生命周期来阅读代码

Connect组件方法组成:方法虽然很多,但是我们只需要紧跟react生命周期函数去了解代码,而其他方法都是在生命周期函数调用的。

class Connect extends Component {
      shouldComponentUpdate() {}
      constructor(props,context) {}    
      computeStateProps(store,props) {}    
      configureFinalMapState(store,props) {}    
      computedispatchProps(store,props) {}    
      configureFinalMapdispatch(store,props) {}    
      updateStatePropsIfNeeded() {}
      updatedispatchPropsIfNeeded() {}    
      updateMergedPropsIfNeeded() {}    
      isSubscribed() {}    
      trySubscribe() {}    
      tryUnsubscribe() {}    
      componentDidMount() {}    
      componentwillReceiveProps(nextProps) {}    
      componentwillUnmount() {}    
      clearCache() {}    
      handleChange() {}    
      getWrappedInstance() {}
      render() {}
}

简单了解react生命周期的函数执行顺序:

初次渲染:render => componentDidMount

当state更新时:componentwillReceiveProps => shouldComponentUpdate => render

render:进入Connect组件执行的时候,先进入render方法

render() {
        const {haveOwnPropsChanged,renderedElement} = this

        this.haveOwnPropsChanged = false
        this.hasstoreStateChanged = false
        this.haveStatePropsBeenPrecalculated = false
        this.statePropsPrecalculationError = null

        if (statePropsPrecalculationError) {
          throw statePropsPrecalculationError
        }

        let shouldUpdateStateProps = true
        let shouldUpdatedispatchProps = true
        if (pure && renderedElement) {
          shouldUpdateStateProps = hasstoreStateChanged || (
            haveOwnPropsChanged && this.doStatePropsDependONownProps
          )
          shouldUpdatedispatchProps =
            haveOwnPropsChanged && this.dodispatchPropsDependONownProps
        }

        let haveStatePropsChanged = false
        let havedispatchPropsChanged = false
        if (haveStatePropsBeenPrecalculated) {
          haveStatePropsChanged = true
        } else if (shouldUpdateStateProps) {
          haveStatePropsChanged = this.updateStatePropsIfNeeded()
        }
        if (shouldUpdatedispatchProps) {
          havedispatchPropsChanged = this.updatedispatchPropsIfNeeded()
        }

        let haveMergedPropsChanged = true
        if (haveStatePropsChanged || havedispatchPropsChanged || haveOwnPropsChanged) {
          haveMergedPropsChanged = this.updateMergedPropsIfNeeded()
        } else {
          haveMergedPropsChanged = false
        }

        if (!haveMergedPropsChanged && renderedElement) {
          return renderedElement
        }

        if (withRef) {
          this.renderedElement = createElement(WrappedComponent,this.mergedProps
          )
        }

a、首先定义了5个成员变量,在Connect组件内部的任意函数位置可以访问到this定义的成员变量。

const {haveOwnPropsChanged,renderedElement} = this

//上面的代码等于下面的写法,this指当前的组件对象。

//判断新传入的props和当前的是否相等,是bool值
var haveOwnPropsChanged = this.haveOwnPropsChanged; 
//当state更新时,改变hasstoreStateChanged的状态,是bool值
var hasstoreStateChanged = this.hasstoreStateChanged;
//表示state和props已经提前计算改变,也是bool值
var haveStatePropsBeenPrecalculated = this.haveStatePropsBeenPrecalculated;
//如果state和props更新时出现错误,则抛出statePropsPrecalculationError异常
var statePropsPrecalculationError = this.statePropsPrecalculationError;
//将要渲染的react组件
var renderedElement = this.renderedElement;

b、给成员变量设置认值。认值要么是false,要么是null。

this.haveOwnPropsChanged = false
this.hasstoreStateChanged = false
this.haveStatePropsBeenPrecalculated = false
this.statePropsPrecalculationError = null

c、抛出异常:初次渲染时,statePropsPrecalculationError为null,不会抛出异常,当执行state和props更新出现异常时,会抛出错误

if (statePropsPrecalculationError) {
      throw statePropsPrecalculationError
}

我们追踪到statePropsPrecalculationError的赋值是在handleChange()里面执行的,受到haveStatePropsChanged的结果影响。当haveStatePropsChanged出现错误时,就把报错内容赋值给statePropsPrecalculationError。

if (haveStatePropsChanged === errorObject) {
      this.statePropsPrecalculationError = errorObject.value
}

d、定义shouldUpdateStateProps和shouldUpdatedispatchProps认为true前者表示认允许更新state和props,后者表示认允许更新dispatch。
pure:options的配置项,初始值为true。
shouldUpdateStateProps:我们看到 || 符号,只要左右2边满足一个为true,则返回true,如果2个都是false,则返回false。
shouldUpdatedispatchProps:同时满足haveOwnPropsChanged、dodispatchPropsDependONownProps为true,则返回true,否则返回false。

let shouldUpdateStateProps = true
    let shouldUpdatedispatchProps = true
    if (pure && renderedElement) {
        shouldUpdateStateProps = hasstoreStateChanged ||
 (haveOwnPropsChanged && this.doStatePropsDependONownProps)
        shouldUpdatedispatchProps = haveOwnPropsChanged && this.dodispatchPropsDependONownProps
     }

e、上面几个步骤都是定义state和props的各种状态的变量,目的是为了判断render方法返回怎样的renderedElement。

//如果haveMergedPropsChanged为false,并且renderedElement不为null,则返回renderedElement
//这段代码在初次渲染是不会执行,只有在更新state和props的时候执行
if (!haveMergedPropsChanged && renderedElement) {
    return renderedElement
}

//haveMergedPropsChanged由updateMergedPropsIfNeeded方法的返回值控制,如果mergedProps等于nextMergedProps,返回false,不相等则返回true,表示应该更新state和props
updateMergedPropsIfNeeded() {
    const nextMergedProps = computeMergedProps(this.stateProps,this.props)
    if (this.mergedProps && checkMergedEquals && shallowEqual(nextMergedProps,this.mergedProps)) {
      return false
    }

    this.mergedProps = nextMergedProps
    return true
  }

初次进入组件最先渲染的返回值是下面这段:

if (withRef) {
          this.renderedElement = createElement(WrappedComponent,this.mergedProps
          )
        }

connect渲染结果:在你绑定的组件外层包裹了Connect组件,看下面的图你应该能更加清晰的了解connect做的事情。

componentwillReceiveProps:组件接收到新的state。如果pure为false,并且nextProps和this.props不相等,则设置this.haveOwnPropsChanged为true。

componentwillReceiveProps(nextProps) {
        if (!pure || !shallowEqual(nextProps,this.props)) {
          this.haveOwnPropsChanged = true
        }
      }

shouldComponentUpdate():判断组件是否允许更新。

shouldComponentUpdate() {
        return !pure || this.haveOwnPropsChanged || this.hasstoreStateChanged
      }

componentDidMount():组件初次渲染完成,执行订阅更新

componentDidMount() {
        this.trySubscribe()
      }

componentwillUnmount():组件卸载时恢复状态。

componentwillUnmount() {
        this.tryUnsubscribe()
        this.clearCache()
      }

      clearCache() {
        this.dispatchProps = null
        this.stateProps = null
        this.mergedProps = null
        this.haveOwnPropsChanged = true
        this.hasstoreStateChanged = true
        this.haveStatePropsBeenPrecalculated = false
        this.statePropsPrecalculationError = null
        this.renderedElement = null
        this.finalMapdispatchToProps = null
        this.finalMapStatetoProps = null
      }

8、总结
如果看到这里,你还没有理清思路,那么可以看完总结再回过头去理解源码。

connect方法做的事情是将state和dispatch绑定到Connect组件的参数上,然后Connect组件将你当前的App组件封装起来,使得App组件可以通过props获取到父组件Connect传递的state和props。

这也就是为什么你可以在自己写的组件上面直接通过this.props访问到state和action。有的人是通过store去读取state和dispatch action,也是一样的道理。

从connect方法的实现,我们看到了非常多react组件的影子,生命周期,props传递,context上下文。

对比Provider组件:

Provider是顶层组件的作用,将store作为上下文提供给全局共享,而Connect组件是局部组件,将某个react组件包装起来,传递指定的state和props给该组件访问。

相关文章

一、前言 在组件方面react和Vue一样的,核心思想玩的就是组件...
前言: 前段时间学习完react后,刚好就接到公司一个react项目...
前言: 最近收到组长通知我们项目组后面新开的项目准备统一技...
react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom...