React系列——react-redux之connect方法解析

 

 

connect簡介

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

react-redux僅有2個API,Provider和connect,Provider提供的是一個頂層容器的做用,實現store的上下文傳遞。html

connect方法比較複雜,雖然代碼只有368行,可是爲redux中經常使用的功能實現了和react鏈接的創建。java

一個基礎的connect方法以下:react

connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) 

爲何咱們須要react-redux?

熟悉redux的人可能知道,redux是數據存儲和管理的工具,可是想要在react中使用redux,並不能直接將store、action和react組件創建鏈接,因此就須要react-redux來結合react和redux。redux

react-redux文件體積很是小,你徹底不須要擔憂給你的項目帶來太多的垃圾代碼。segmentfault

從何處開始解析react-redux源碼?

一、在JavaScript中,讀懂別人的代碼文件,你首先應該看的是函數的入口。設計模式

二、找到函數入口,而後看有哪些參數。數組

三、看看導入了哪些額外的插件,每一個插件的做用大概預測一下。app

四、進入函數體進行解讀。在react插件中解讀函數有一個好處,就是react插件大部分都是採用了react組件的寫法,你能夠在react插件中看到不少react組件的影子。而不是像jQuery那樣處處都是擴展性的方法,每一個方法都有本身的設計模式,沒有統一的規律可循。ide

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, dispatchProps, 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, mapDispatchToProps, mergeProps, 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, dispatchProps, parentProps) {
      const mergedProps = finalMergeProps(stateProps, dispatchProps, 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)
  }
}

咱們按照上面介紹的解析步驟來一步步有序的分析源碼。

一、查看函數入口,以及須要傳入的參數。

若是隻是看這樣一個函數體,咱們沒法得知每一個參數究竟是什麼?有什麼做用?可是,咱們能夠先結合使用的demo初步瞭解各個參數的做用。

export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, 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({}, ownProps, stateProps, dispatchProps),顧名思義,mergeProps是合併的意思,將state合併後傳遞給組件。

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

options:經過配置項能夠更加詳細的定義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' 

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個參數,targetComponentsourceComponentcustomStatics,首先判斷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, b, c, d, e, 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,
          `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}".` ) 

三、定義幾個參數默認值常量

當你沒有給組件綁定state和dispatch的時候,就執行默認的配置。

defaultMapStateToProps:傳入state,返回空對象

defaultMapDispatchToProps: 傳入dispatch,返回dispatch對象

defaultMergeProps:傳入stateProps, dispatchProps, parentProps,返回當前傳入的對象。

const defaultMapStateToProps = state => ({}) const defaultMapDispatchToProps = dispatch => ({ dispatch }) const defaultMergeProps = (stateProps, dispatchProps, parentProps) => ({ ...parentProps, ...stateProps, ...dispatchProps }) 

四、getDisplayName方法

返回當前傳入的組件名

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

五、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 

六、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) } } 

七、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, 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 ) } 

a、首先定義了5個成員變量,在Connect組件內部的任意函數位置能夠訪問到this定義的成員變量。

const {haveOwnPropsChanged, hasStoreStateChanged, haveStatePropsBeenPrecalculated, statePropsPrecalculationError, 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.dispatchProps, 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, ref: 'wrappedInstance' }) } else { this.renderedElement = createElement(WrappedComponent, this.mergedProps ) }

connect渲染結果:在你綁定的組件外層包裹了Connect組件,看下面的圖你應該能更加清晰的瞭解connect作的事情。

clipboard.png

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 } 

八、總結
若是看到這裏,你尚未理清思路,那麼能夠看完總結再回過頭去理解源碼。

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給該組件訪問。

相關文章
相關標籤/搜索