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, 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、檢視函數入口,以及需要傳入的參數。
如果隻是看這樣一個函數體,我們無法得知每個參數到底是什麼?有什麼作用?但是,我們可以先結合使用的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的行為,通常隻需要執行預設值。
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, 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}".`
)
3、定義幾個參數預設值常量
當你沒有給元件綁定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
})
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, 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做的事情。
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給該元件通路。