react-redux Connect方法

https://segmentfault.com/a/1190000010188279

connect簡介

前方高能預(yù)警,有耐心才能看完文章??!

react-redux僅有2個(gè)API,Provider和connect,Provider提供的是一個(gè)頂層容器的作用,實(shí)現(xiàn)store的上下文傳遞。

connect方法比較復(fù)雜,雖然代碼只有368行,但是為redux中常用的功能實(shí)現(xiàn)了和react連接的建立。

一個(gè)基礎(chǔ)的connect方法如下:

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

為什么我們需要react-redux?

熟悉redux的人可能知道,redux是數(shù)據(jù)存儲(chǔ)和管理的工具,但是想要在react中使用redux,并不能直接將store、action和react組件建立連接,所以就需要react-redux來結(jié)合react和redux。

react-redux文件體積非常小,你完全不需要擔(dān)心給你的項(xiàng)目帶來太多的垃圾代碼。

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

1、在JavaScript中,讀懂別人的代碼文件,你首先應(yīng)該看的是函數(shù)的入口。

2、找到函數(shù)入口,然后看有哪些參數(shù)。

3、看看導(dǎo)入了哪些額外的插件,每個(gè)插件的作用大概預(yù)測一下。

4、進(jìn)入函數(shù)體進(jìn)行解讀。在react插件中解讀函數(shù)有一個(gè)好處,就是react插件大部分都是采用了react組件的寫法,你可以在react插件中看到很多react組件的影子。而不是像jQuery那樣到處都是擴(kuò)展性的方法,每個(gè)方法都有自己的設(shè)計(jì)模式,沒有統(tǒng)一的規(guī)律可循。

react-redux使用場景

下面這個(gè)官方例子展示了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源碼解析

源碼有點(diǎn)長,你可以選擇性的查看:

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

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

1、查看函數(shù)入口,以及需要傳入的參數(shù)。

如果只是看這樣一個(gè)函數(shù)體,我們無法得知每個(gè)參數(shù)到底是什么?有什么作用?但是,我們可以先結(jié)合使用的demo初步了解各個(gè)參數(shù)的作用。

export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {}

mapStateToProps:傳入所有state,返回指定的state數(shù)據(jù)。

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

mapDispatchToProps:傳入dispatch,返回使用bindActionCreators()綁定的action方法。我們不再這里討論bindActionCreators的用法,這個(gè)知識將會(huì)放到redux解析的文章中。

function mapDispatchToProps(dispatch) {
  return bindActionCreators(Object.assign({}, todoActionCreators, counterActionCreators), dispatch)
}

mergeProps:mergeProps如果不指定,則默認(rèn)返回 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:通過配置項(xiàng)可以更加詳細(xì)的定義connect的行為,通常只需要執(zhí)行默認(rèn)值。

2、查看導(dǎo)入了哪些插件

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類似,需要?jiǎng)?chuàng)建一個(gè)Connect組件。

storeShape:通過了redux常用API的類型驗(yàn)證。

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

shallowEqual:這個(gè)文件的作用是傳入2個(gè)對象,首先比較對象是否一致,如果一致,則返回true,如果不一致,則獲取2個(gè)對象的key數(shù)組,判斷2個(gè)對象key數(shù)組的長度是否相等,如果不相等,返回false,如果相等,最后用for循環(huán)遍歷A對象的key,如果當(dāng)前的遍歷值不存在于B的key中或者A對象的當(dāng)前key的value不等于B對象的當(dāng)前key的value,則返回false,如果不屬于上面的任何情況,則返回true。(如果認(rèn)為我這段講的迷迷糊糊,你也可以自己理解下面的代碼。)

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的作用是判斷對象里面是否包含某個(gè)屬性。這段代碼的實(shí)際用途是判斷下一個(gè)props和當(dāng)前的props是否一致。

shallowEqual(nextStateProps, this.stateProps)

wrapActionCreators:實(shí)現(xiàn)了bindActionCreators方法綁定action到組件的操作。

import { bindActionCreators } from 'redux'

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

函數(shù)使用方法

wrapActionCreators(mapDispatchToProps)

warning:在控制臺(tái)打印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:這段代碼有點(diǎn)神奇,REACT_STATICS是一堆react的常用方法,KNOWN_STATICS是函數(shù)的一些屬性。

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

我們首先從函數(shù)入口解讀,入口傳入了3個(gè)參數(shù),targetComponent, sourceComponent, customStatics,首先判斷sourceComponent的類型不是一個(gè)字符串,然后使用getOwnPropertyNames獲取sourceComponent對象的key,返回值是key組成的數(shù)組keys。接著判斷isGetOwnPropertySymbolsAvailable(肯定是true),如果為true,執(zhí)行下面的語句:

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

getOwnPropertySymbols和getOwnPropertyNames作用類似,但是getOwnPropertyNames只是返回字符串類型的key,而getOwnPropertySymbols可以返回Symbol類型的key。然后我們再把2種情況下的key拼接到一個(gè)數(shù)組里面返回新的keys。

然后執(zhí)行for語句,遍歷keys,如果不包含REACT_STATICS中的react的靜態(tài)方法,同時(shí)不包含KNOWN_STATICS中的屬性,同時(shí)不存在customStatics(傳入函數(shù)的第三個(gè)參數(shù)不存在)或者存在但沒有sourceComponent的key,就執(zhí)行:

//將sourceComponent的方法寫入targetComponent中
targetComponent[keys[i]] = sourceComponent[keys[i]];

最后返回targetComponent:

return targetComponent

該方法在connect中的實(shí)際作用是:將WrappedComponent內(nèi)的react靜態(tài)方法綁定到Connect組件上。

hoistStatics(Connect, WrappedComponent)

invariant:我們看到invariant傳入了好幾個(gè)參數(shù),第一個(gè)if語句表示如果不是生產(chǎn)環(huán)境,并且format沒有定義,就拋出異常。第二個(gè)if表示如果condition未定義,同時(shí)format未定義,就拋出error,如果condition不存在但format存在,拋出另外的錯(cuò)誤。(總結(jié)就是一個(gè)錯(cuò)誤檢查機(jī)制)

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;

該方法實(shí)際用途:檢查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}".`
        )

3、定義幾個(gè)參數(shù)默認(rèn)值常量

當(dāng)你沒有給組件綁定state和dispatch的時(shí)候,就執(zhí)行默認(rèn)的配置。

defaultMapStateToProps:傳入state,返回空對象

defaultMapDispatchToProps: 傳入dispatch,返回dispatch對象

defaultMergeProps:傳入stateProps, dispatchProps, parentProps,返回當(dāng)前傳入的對象。

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

4、getDisplayName方法

返回當(dāng)前傳入的組件名

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

5、tryCatch方法
給fn函數(shù)指定上下文。

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

使用場景:在connect內(nèi)調(diào)用tryCatch給updateStatePropsIfNeeded方法指定當(dāng)前的上下文

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函數(shù)解析思路
connect函數(shù)是核心,我們需要大概了解函數(shù)做的事情,才能更好的讀懂源碼。
既然是函數(shù),那就有返回值,connect()返回值是Connect組件(請注意大小寫的區(qū)別)。

通俗點(diǎn)理解,使用connect可以把state和dispatch綁定到react組件,使得組件可以訪問到redux的數(shù)據(jù)。
??吹较旅孢@種寫法:

export default connect(mapStateToProps)(TodoApp)

我把connect的核心實(shí)現(xiàn)簡化提取出來,是下面這種形式:WrappedComponent參數(shù)對應(yīng)的就是TodoApp。函數(shù)最終返回的是將state和dispatch綁定到Connect之后的新組件。

funtion connect(mapStateToProps) {
    return function wrapWithConnect(WrappedComponent) {
        class Connect extends Component {

        }
        return hoistStatics(Connect, WrappedComponent)
    }
}

7、Connect組件執(zhí)行

既然已經(jīng)知道connect函數(shù)返回的是Connect組件,而Connect組件繼承于react,我們就可以按照react的生命周期來閱讀代碼。

Connect組件方法組成:方法雖然很多,但是我們只需要緊跟react生命周期函數(shù)去了解代碼,而其他方法都是在生命周期函數(shù)中調(diào)用的。

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生命周期的函數(shù)執(zhí)行順序:

初次渲染:render => componentDidMount

當(dāng)state更新時(shí):componentWillReceiveProps => shouldComponentUpdate => render

render:進(jìn)入Connect組件執(zhí)行的時(shí)候,先進(jìn)入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個(gè)成員變量,在Connect組件內(nèi)部的任意函數(shù)位置可以訪問到this定義的成員變量。

const {haveOwnPropsChanged, hasStoreStateChanged, haveStatePropsBeenPrecalculated, statePropsPrecalculationError, renderedElement} = this

//上面的代碼等于下面的寫法,this指當(dāng)前的組件對象。

//判斷新傳入的props和當(dāng)前的是否相等,是bool值
var haveOwnPropsChanged = this.haveOwnPropsChanged; 
//當(dāng)state更新時(shí),改變hasStoreStateChanged的狀態(tài),是bool值
var hasStoreStateChanged = this.hasStoreStateChanged;
//表示state和props已經(jīng)提前計(jì)算改變,也是bool值
var haveStatePropsBeenPrecalculated = this.haveStatePropsBeenPrecalculated;
//如果state和props更新時(shí)出現(xiàn)錯(cuò)誤,則拋出statePropsPrecalculationError異常
var statePropsPrecalculationError = this.statePropsPrecalculationError;
//將要渲染的react組件
var renderedElement = this.renderedElement;

b、給成員變量設(shè)置默認(rèn)值。默認(rèn)值要么是false,要么是null。

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

c、拋出異常:初次渲染時(shí),statePropsPrecalculationError為null,不會(huì)拋出異常,當(dāng)執(zhí)行state和props更新出現(xiàn)異常時(shí),會(huì)拋出錯(cuò)誤。

if (statePropsPrecalculationError) {
      throw statePropsPrecalculationError
}

我們追蹤到statePropsPrecalculationError的賦值是在handleChange()里面執(zhí)行的,受到haveStatePropsChanged的結(jié)果影響。當(dāng)haveStatePropsChanged出現(xiàn)錯(cuò)誤時(shí),就把報(bào)錯(cuò)內(nèi)容賦值給statePropsPrecalculationError。

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

d、定義shouldUpdateStateProps和shouldUpdateDispatchProps:默認(rèn)為true前者表示默認(rèn)允許更新state和props,后者表示默認(rèn)允許更新dispatch。
pure:options的配置項(xiàng),初始值為true。
shouldUpdateStateProps:我們看到 || 符號,只要左右2邊滿足一個(gè)為true,則返回true,如果2個(gè)都是false,則返回false。
shouldUpdateDispatchProps:同時(shí)滿足haveOwnPropsChanged、doDispatchPropsDependOnOwnProps為true,則返回true,否則返回false。

    let shouldUpdateStateProps = true
    let shouldUpdateDispatchProps = true
    if (pure && renderedElement) {
        shouldUpdateStateProps = hasStoreStateChanged ||
 (haveOwnPropsChanged && this.doStatePropsDependOnOwnProps)
        shouldUpdateDispatchProps = haveOwnPropsChanged && this.doDispatchPropsDependOnOwnProps
     }

e、上面幾個(gè)步驟都是定義state和props的各種狀態(tài)的變量,目的是為了判斷render方法返回怎樣的renderedElement。

//如果haveMergedPropsChanged為false,并且renderedElement不為null,則返回renderedElement
//這段代碼在初次渲染是不會(huì)執(zhí)行,只有在更新state和props的時(shí)候執(zhí)行
if (!haveMergedPropsChanged && renderedElement) {
    return renderedElement
}

//haveMergedPropsChanged由updateMergedPropsIfNeeded方法的返回值控制,如果mergedProps等于nextMergedProps,返回false,不相等則返回true,表示應(yīng)該更新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
  }

初次進(jìn)入組件最先渲染的返回值是下面這段:

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

connect渲染結(jié)果:在你綁定的組件外層包裹了Connect組件,看下面的圖你應(yīng)該能更加清晰的了解connect做的事情。

image.png

componentWillReceiveProps:組件接收到新的state。如果pure為false,并且nextProps和this.props不相等,則設(shè)置this.haveOwnPropsChanged為true。

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

shouldComponentUpdate():判斷組件是否允許更新。

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

componentDidMount():組件初次渲染完成,執(zhí)行訂閱更新

componentDidMount() {
        this.trySubscribe()
      }

componentWillUnmount():組件卸載時(shí)恢復(fù)狀態(tài)。

    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、總結(jié)
如果看到這里,你還沒有理清思路,那么可以看完總結(jié)再回過頭去理解源碼。

connect方法做的事情是將state和dispatch綁定到Connect組件的參數(shù)上,然后Connect組件將你當(dāng)前的App組件封裝起來,使得App組件可以通過props獲取到父組件Connect傳遞的state和props。

這也就是為什么你可以在自己寫的組件上面直接通過this.props訪問到state和action。有的人是通過store去讀取state和dispatch action,也是一樣的道理。

從connect方法的實(shí)現(xiàn),我們看到了非常多react組件的影子,生命周期,props傳遞,context上下文。

對比Provider組件:

Provider是頂層組件的作用,將store作為上下文提供給全局共享,而Connect組件是局部組件,將某個(gè)react組件包裝起來,傳遞指定的state和props給該組件訪問。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容