轉眼間2017年已經過了一半了,看到之前有人問是否完成了自己半年的計劃,答案是:當然沒有啦。感覺自己現在對技術產生了敬畏,因為要學習的知識是在是太多了,而自己的時間和精力卻很難達到目標,目前處在比較焦慮的狀態。自己是年初進入掘金的,半年內雖然文章的閱讀量不錯但是關注度太低,半年就混了40個關注,說來真是慚愧。
扯遠了,我們言歸正傳,上次的文章Redux:百行程式碼千行文件解釋了Redux內部的運作原理。但是我們在React中很少會直接搭配使用Redux,而是通過React-Redux繫結React與Redux。這篇文章我們我們將瞭解React-Redux其中的奧祕。在閱讀之前希望你有React-Redux的使用經驗,否則這篇文章可能不太適合你。
首先我們可以看看React-Redux的原始碼目錄結構,大致看一下,做到心裡有數。
.
├── components
│ ├── Provider.js
│ └── connectAdvanced.js
├── connect
│ ├── connect.js
│ ├── mapDispatchToProps.js
│ ├── mapStateToProps.js
│ ├── mergeProps.js
│ ├── selectorFactory.js
│ ├── verifySubselectors.js
│ └── wrapMapToProps.js
├── index.js
└── utils
├── PropTypes.js
├── Subscription.js
├── shallowEqual.js
├── verifyPlainObject.js
├── warning.js
└── wrapActionCreators.js
首先來看一下index.js:
import Provider, { createProvider } from './components/Provider'
import connectAdvanced from './components/connectAdvanced'
import connect from './connect/connect'
export { Provider, createProvider, connectAdvanced, connect }複製程式碼
我們可以看出來,React-Redux對外提供的API有四個:Provider
、createProvider
,connectAdvanced
,connect
。我們將從connectAdvanced
開始介紹。
connectAdvanced
其實我在看React-Redux原始碼之前都不知道有這個API,為了方便後面的原始碼理解,我們介紹一下connectAdvanced
:
connectAdvanced(selectorFactory, [connectOptions])
connectAdvanced
用來連線元件到Redux的store上。是connect
函式的基礎,但並沒有規定如何將state
、props
、dispatch
處理傳入最終的props
中。connectAdvanced
並沒有對產生的props做快取來優化效能,都留給了呼叫者去實現。connectAdvanced
並沒有修改傳入的元件類,而是返回一個新的、連線到store的元件類。
引數:
- selectorFactory(dispatch, factoryOptions): selector(state, ownProps): props (Function),用來初始化
selector
函式(在每次例項的建構函式中)。selector
函式在每次connector component
需要計算新的props(在元件傳遞新的props和store中資料發生改變時會計算新的props)都會被呼叫。selector
函式會返回純物件(plain object),這個物件會作為props傳遞給被包裹的元件(WrappedComponent) - [connectOptions] (Object) 如果定義了該引數,用來進一步定製connector:
1. [getDisplayName] (Function): 用來計算connector component的displayName。
2. [methodName] (String) 用來在錯誤資訊中顯示,預設值為connectAdvanced
3. [renderCountProp] (String): 如果定義了這個屬性,以該屬性命名的值會被以props傳遞給包裹元件。該值是元件渲染的次數,可以追蹤不必要的渲染。
4. [shouldHandleStateChanges] (Boolean): 控制conntector
元件是否應該訂閱redux store中的state變化。
5. [storeKey] (String): 你想要從context和props獲得store的key值,只有在需要多個store的情況下才會用到(當然,這並不是明智的選擇)。預設是store
。
6. [withRef] (Boolean): 如果是true
,儲存被包裹元件的例項,並可以通過函式getWrappedInstance
獲得該例項,預設值為false
。
7. 在connectOptions
中額外的屬性會被傳遞給selectorFactory
函式的factoryOptions
屬性。
返回:
函式返回一個高階元件,該高階元件將從store的state中構建的props傳遞給被包裹元件。
例如:
// 按照使用者資訊選擇性傳入todos的部分資訊
import * as actionCreators from './actionCreators'
import { bindActionCreators } from 'redux'
function selectorFactory(dispatch) {
let ownProps = {}
let result = {}
const actions = bindActionCreators(actionCreators, dispatch)
const addTodo = (text) => actions.addTodo(ownProps.userId, text)
return (nextState, nextOwnProps) => {
const todos = nextState.todos[nextProps.userId]
const nextResult = { ...nextOwnProps, todos, addTodo }
ownProps = nextOwnProps
if (!shallowEqual(result, nextResult)) result = nextResult
return result
}
}
export default connectAdvanced(selectorFactory)(TodoApp)複製程式碼
講了這麼多,我們看看connectAdvanced
是如何實現的,一開始本來想把所有的程式碼都列出來,但是感覺直接列出200多行的程式碼看著確實不方便,所以我們還是一部分一部分介紹:
//程式碼整體結構
function connectAdvanced(
selectorFactory,
{
getDisplayName = name => `ConnectAdvanced(${name})`,
methodName = 'connectAdvanced',
renderCountProp = undefined,
shouldHandleStateChanges = true,
storeKey = 'store',
withRef = false,
...connectOptions
} = {}
) {
return function wrapWithConnect(WrappedComponent) {
class Connect extends Component {
//......
return hoistStatics(Connect, WrappedComponent)
}
}複製程式碼
函式接受兩個引數:selectorFactory
與connectOptions
(可選),返回一個高階元件wrapWithConnect
(以屬性代理方式實現),高階元件中建立了元件類Connect
, 最後返回了hoistStatics(Connect, WrappedComponent)
。其中hoistStatics
來源於:
import hoistStatics from 'hoist-non-react-statics'複製程式碼
作用是將WrappedComponent
中的非React特定的靜態屬性(例如propTypes
就是React的特定靜態屬性)賦值到Connect
。作用有點類似於Object.assign
,但是僅複製非React特定的靜態屬性。
其實對於React-Redux之所以可以使得Provider
中的任何子元件訪問到Redux中的store
並訂閱store
,無非是利用context
,使得所有子元件都能訪問store
。更進一步,我們看看高階元件時如何實現:
let hotReloadingVersion = 0
const dummyState = {}
function noop() {}
function connectAdvanced(
selectorFactory,
{
getDisplayName = name => `ConnectAdvanced(${name})`,
methodName = 'connectAdvanced',
renderCountProp = undefined,
shouldHandleStateChanges = true,
storeKey = 'store',
withRef = false,
...connectOptions
} = {}
) {
const subscriptionKey = storeKey + 'Subscription'
const version = hotReloadingVersion++
const contextTypes = {
[storeKey]: storeShape,
[subscriptionKey]: subscriptionShape,
}
const childContextTypes = {
[subscriptionKey]: subscriptionShape,
}
return function wrapWithConnect(WrappedComponent) {
const wrappedComponentName = WrappedComponent.displayName
|| WrappedComponent.name
|| 'Component'
const displayName = getDisplayName(wrappedComponentName)
const selectorFactoryOptions = {
...connectOptions,
getDisplayName,
methodName,
renderCountProp,
shouldHandleStateChanges,
storeKey,
withRef,
displayName,
wrappedComponentName,
WrappedComponent
}
class Connect extends Component {
}
return hoistStatics(Connect, WrappedComponent)
}
}複製程式碼
上面的程式碼並沒有什麼難以理解的,connectAdvanced
中定義了subscriptionKey
、version
以及為Connect
元件定義的contextTypes
與childContextTypes
(不瞭解context
的同學可以看這裡)。在高階元件中所作的就是定義組裝了selectorFactory
所用到的引數selectorFactoryOptions
。接下來介紹最重要的元件類Connect
:
class Connect extends Component {
constructor(props, context) {
super(props, context)
this.version = version
this.state = {}
this.renderCount = 0
this.store = props[storeKey] || context[storeKey]
this.propsMode = Boolean(props[storeKey])
this.setWrappedInstance = this.setWrappedInstance.bind(this)
this.initSelector()
this.initSubscription()
}
getChildContext() {
const subscription = this.propsMode ? null : this.subscription
return { [subscriptionKey]: subscription || this.context[subscriptionKey] }
}
componentDidMount() {
if (!shouldHandleStateChanges) return
this.subscription.trySubscribe()
this.selector.run(this.props)
if (this.selector.shouldComponentUpdate) this.forceUpdate()
}
componentWillReceiveProps(nextProps) {
this.selector.run(nextProps)
}
shouldComponentUpdate() {
return this.selector.shouldComponentUpdate
}
componentWillUnmount() {
if (this.subscription) this.subscription.tryUnsubscribe()
this.subscription = null
this.notifyNestedSubs = noop
this.store = null
this.selector.run = noop
this.selector.shouldComponentUpdate = false
}
getWrappedInstance() {
return this.wrappedInstance
}
setWrappedInstance(ref) {
this.wrappedInstance = ref
}
initSelector() {
const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
this.selector = makeSelectorStateful(sourceSelector, this.store)
this.selector.run(this.props)
}
initSubscription() {
if (!shouldHandleStateChanges) return
const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]
this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))
this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
}
onStateChange() {
this.selector.run(this.props)
if (!this.selector.shouldComponentUpdate) {
this.notifyNestedSubs()
} else {
this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate
this.setState(dummyState)
}
}
notifyNestedSubsOnComponentDidUpdate() {
this.componentDidUpdate = undefined
this.notifyNestedSubs()
}
isSubscribed() {
return Boolean(this.subscription) && this.subscription.isSubscribed()
}
addExtraProps(props) {
if (!withRef && !renderCountProp && !(this.propsMode && this.subscription)) return props
const withExtras = { ...props }
if (withRef) withExtras.ref = this.setWrappedInstance
if (renderCountProp) withExtras[renderCountProp] = this.renderCount++
if (this.propsMode && this.subscription) withExtras[subscriptionKey] = this.subscription
return withExtras
}
render() {
const selector = this.selector
selector.shouldComponentUpdate = false
if (selector.error) {
throw selector.error
} else {
return createElement(WrappedComponent, this.addExtraProps(selector.props))
}
}
}
Connect.WrappedComponent = WrappedComponent
Connect.displayName = displayName
Connect.childContextTypes = childContextTypes
Connect.contextTypes = contextTypes
Connect.propTypes = contextTypes
if (process.env.NODE_ENV !== 'production') {
Connect.prototype.componentWillUpdate = function componentWillUpdate() {
// We are hot reloading!
if (this.version !== version) {
this.version = version
this.initSelector()
if (this.subscription) this.subscription.tryUnsubscribe()
this.initSubscription()
if (shouldHandleStateChanges) this.subscription.trySubscribe()
}
}
}複製程式碼
我們首先來看建構函式:
constructor(props, context) {
super(props, context)
this.version = version
this.state = {}
this.renderCount = 0
this.store = props[storeKey] || context[storeKey]
this.propsMode = Boolean(props[storeKey])
this.setWrappedInstance = this.setWrappedInstance.bind(this)
this.initSelector()
this.initSubscription()
}複製程式碼
首先我們先看看用來初始化selector
的initSelector
函式:
//Connect類方法
initSelector() {
const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
this.selector = makeSelectorStateful(sourceSelector, this.store)
this.selector.run(this.props)
}
//connectAdvanced外定義的函式
function makeSelectorStateful(sourceSelector, store) {
// wrap the selector in an object that tracks its results between runs.
const selector = {
run: function runComponentSelector(props) {
try {
const nextProps = sourceSelector(store.getState(), props)
if (nextProps !== selector.props || selector.error) {
selector.shouldComponentUpdate = true
selector.props = nextProps
selector.error = null
}
} catch (error) {
selector.shouldComponentUpdate = true
selector.error = error
}
}
}
return selector
}複製程式碼
我們知道,selector
的主要作用是用來從store
中的state
和ownProps
中計算新的props,並返回純物件(plain object),這個物件會作為props傳遞給被包裹的元件(WrappedComponent)。在initSelector
中,首先呼叫selectorFactory
從而初始化sourceSelector
,我們並不會直接呼叫sourceSelector
,而是為了程式的健壯,通過將sourceSelector
作為引數呼叫makeSelectorStateful
,返回更加安全的selector
。從此之後,我們想要生成新的props
只需要呼叫selector.run
函式。在selector.run
函式中對sourceSelector
的異常做了處理,並用sourceSelector.error
記錄是否存在異常。sourceSelector.shouldComponentUpdate
用來根據前後兩次返回的props
是否相同,從而記錄是否應該重新整理元件,這就為後期的效能提升留出了空間,只要在前後資料相同時,我們就返回同一個物件,使得shouldComponentUpdate
為false
,就可以避免不必要的重新整理,當然這不是我們selector
的職責,而是sourceSelector
所需要做的。每次返回的新的props
都會記錄在selector.props
以備後用。
再看initSubscription
函式之前,我們需要先了解一下Subscription
類:
// 為連線到redux的store的元件以及巢狀的後代元件封裝訂閱邏輯,以確保祖先元件在後代元件之前重新整理
const CLEARED = null
const nullListeners = { notify() {} }
function createListenerCollection() {
//程式碼邏輯來源與store中
let current = []
let next = []
return {
clear() {
next = CLEARED
current = CLEARED
},
notify() {
const listeners = current = next
for (let i = 0; i < listeners.length; i++) {
listeners[i]()
}
},
subscribe(listener) {
let isSubscribed = true
if (next === current) next = current.slice()
next.push(listener)
return function unsubscribe() {
if (!isSubscribed || current === CLEARED) return
isSubscribed = false
if (next === current) next = current.slice()
next.splice(next.indexOf(listener), 1)
}
}
}
}
export default class Subscription {
constructor(store, parentSub, onStateChange) {
this.store = store
this.parentSub = parentSub
this.onStateChange = onStateChange
this.unsubscribe = null
this.listeners = nullListeners
}
addNestedSub(listener) {
this.trySubscribe()
return this.listeners.subscribe(listener)
}
notifyNestedSubs() {
this.listeners.notify()
}
isSubscribed() {
return Boolean(this.unsubscribe)
}
trySubscribe() {
if (!this.unsubscribe) {
this.unsubscribe = this.parentSub
? this.parentSub.addNestedSub(this.onStateChange)
: this.store.subscribe(this.onStateChange)
this.listeners = createListenerCollection()
}
}
tryUnsubscribe() {
if (this.unsubscribe) {
this.unsubscribe()
this.unsubscribe = null
this.listeners.clear()
this.listeners = nullListeners
}
}
}複製程式碼
首先我們先看函式createListenerCollection
,這邊的程式碼邏輯和redux
中的listener
邏輯一致,可以瞭解一下之前的文章Redux:百行程式碼千行文件。createListenerCollection
通過閉包的方式儲存current
和next
,然後返回
{
clear,
notify,
subscribe
}複製程式碼
作為對外介面,分別用來清除當前儲存的listener、通知、訂閱,其目的就是實現一個監聽者模式。然後類Subscription
封裝了訂閱的邏輯,Subscription
根據建構函式中是否傳入了父級的訂閱類Subscription例項parentSub
,訂閱方法trySubscribe
會有不同的行為。首先看看parentSub
的來源:
//this.propsMode來自於constructor中的this.propsMode = Boolean(props[storeKey]),storeKey預設為`store`
const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]複製程式碼
我們知道Provider
的主要作用就是通過context
向子元件提供store
,而在conectAdvanced
函式的引數connectOptions
中的storeKey
是用來區分從context和props獲得store的key值,只有在需要多個store的情況下才會用到,當然這並不是什麼好事,畢竟Redux追求的是單個store
。例如你設定了storeKey
為otherStore
,那麼就可以通過給wrapWithConnect
返回的元件新增屬性otherStore
,從而注入新的store
。
下面我們區分幾種情況:
情況1:
如果Provider
中的子元件連線到Redux的store,並且祖先元件都沒有連線到Redux的store,也就是說是當前元件是通往根節點的路徑中第一個連線到Redux的store的元件,這時候直接可以使用Redux的store
中的subscribe
方法去訂閱store
的改變。對應於的程式碼是tryUnsubscribe
方法中的
this.store.subscribe(this.onStateChange)。複製程式碼
情況2:
如果當前元件並不是通往根節點的路徑中第一個連線到Redux的store的元件,也就是父元件中存在已經連線到Redux的store的元件。這時候,必須要保證下層的元件響應store
改變的函式呼叫必須晚於父級元件響應store
的函式呼叫,例如在圖中紅色的元件在store更新時是晚於黑色的元件的。程式碼中是如下實現的,在父級元件中,如下:
getChildContext() {
const subscription = this.propsMode ? null : this.subscription
return { [subscriptionKey]: subscription || this.context[subscriptionKey] }
}複製程式碼
因此在子元件(紅色)中就可以通過context
獲得父元件的subscription(也就是parentSub)。這樣在執行tryUnsubscribe
時對應於
this.parentSub.addNestedSub(this.onStateChange)複製程式碼
這樣我們將子元件處理store中state的函式新增到parentSub
中的listener
中。這樣在父元件更新結束後,就可以呼叫this.notifyNestedSubs()
。這樣就保證了更新順序,子元件永遠在父元件更新之後。
情況3:
如上圖所示,右邊的元件是通過屬性prop的方式傳入了store
,那麼元件中的this.store
中的值就是通過以props傳入的store
。假如祖先元素沒有連線到store
的元件,那麼當前元件中parentSub
值就為空。所以訂閱的方式就是以props中的store
:
this.store.subscribe(this.onStateChange)。複製程式碼
情況4:
如上圖所示,右下方的元件的父元件(紫色)是通過props傳入store
的,那麼在父元件(紫色)中有
getChildContext() {
const subscription = this.propsMode ? null : this.subscription
return { [subscriptionKey]: subscription || this.context[subscriptionKey] }
}複製程式碼
父元件對子元件暴露context
,其中context
中的subscriptionKey
屬性值為this.context[subscriptionKey]
,要麼是null
,要麼是祖先元素中非props
方式傳入store
的元件的subscription
。也就是說以props傳入的store
的父元件不會影響子元件的訂閱store
。感覺說的太過於抽象,我們舉個例子:
在上面這個例子中,如果發出dispatch
更新store1,元件A和元件C都會重新整理,元件B不會重新整理。
討論了這麼多,我們可以看一下initSubscription
的實現方式:
initSubscription() {
if (!shouldHandleStateChanges) return
const parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey]
this.subscription = new Subscription(this.store, parentSub, this.onStateChange.bind(this))
this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription)
}複製程式碼
如果當前的store不是以props的方式傳入的,那麼parentSub
是this.context[subscriptionKey]
。如果是以props的方式傳入的,若顯式地給元件以props的方式傳入subscription時,parentSub
值為this.props.subscription
。需要注意的是,我們在initSubscription
中拷貝了當前this.subscription
中的notifyNestedSubs
方法,目的是防止在notify
迴圈過程中元件解除安裝,使得this.subscription
為null
。我們在元件解除安裝時,會將值賦值為一個名為no-loop
的空函式,避免出錯。當然這並不是唯一的解決方法。
接下我們可以看一下Connect
元件中主要生命週期函式:
componentDidMount() {
if (!shouldHandleStateChanges) return
this.subscription.trySubscribe()
this.selector.run(this.props)
if (this.selector.shouldComponentUpdate) this.forceUpdate()
}
componentWillReceiveProps(nextProps) {
this.selector.run(nextProps)
}
shouldComponentUpdate() {
return this.selector.shouldComponentUpdate
}
componentWillUnmount() {
if (this.subscription) this.subscription.tryUnsubscribe()
this.subscription = null
this.notifyNestedSubs = noop
this.store = null
this.selector.run = noop
this.selector.shouldComponentUpdate = false
}複製程式碼
元件在did mount
時會根據可選引數shouldHandleStateChanges
選擇是否訂閱store
的state
改變。元件在接受props時,會使用selector計算新的props並執行相應的宣告週期。shouldComponentUpdate
會根據this.selector
儲存的值shouldComponentUpdate
來判斷是否需要重新整理元件。在元件will mount
時會做相應的清理,防止記憶體洩露。
接著我們介紹其他的類方法:
getWrappedInstance() {
return this.wrappedInstance
}
setWrappedInstance(ref) {
this.wrappedInstance = ref
}複製程式碼
getWrappedInstance
與setWrappedInstance
在可選引數withRef
為true時,獲取或者儲存被包裹元件的例項(ref)。
onStateChange() {
this.selector.run(this.props)
if (!this.selector.shouldComponentUpdate) {
this.notifyNestedSubs()
} else {
this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate
this.setState(dummyState)//dummyState === {}
}
}
notifyNestedSubsOnComponentDidUpdate() {
this.componentDidUpdate = undefined
this.notifyNestedSubs()
}複製程式碼
onStateChange
函式是store發生改變的回撥函式,當回撥onStateChange
方法時,會通過selector計算新的props,如果計算selcetor的結果中shouldComponentUpdate
為false
,表示不需要重新整理當前元件僅需要通知子元件更新。如果shouldComponentUpdate
為true
,會通過設定this.setState({})
來重新整理元件,並使得在元件更新結束之後,通知子元件更新。
addExtraProps(props) {
if (!withRef && !renderCountProp && !(this.propsMode && this.subscription)) return props
const withExtras = { ...props }
if (withRef) withExtras.ref = this.setWrappedInstance
if (renderCountProp) withExtras[renderCountProp] = this.renderCount++
if (this.propsMode && this.subscription) withExtras[subscriptionKey] = this.subscription
return withExtras
}複製程式碼
addExtraProps
函式主要用作為selector
計算出的props
增加新的屬性。例如,ref
屬性用來繫結回撥儲存元件例項的函式setWrappedInstance
,renderCountProp
為當前元件屬性重新整理的次數,subscriptionKey
用來傳遞當前connect
中的subscription
。
render() {
const selector = this.selector
selector.shouldComponentUpdate = false
if (selector.error) {
throw selector.error
} else {
return createElement(WrappedComponent, this.addExtraProps(selector.props))
}
}複製程式碼
render
函式其實就是高階函式中的屬性代理,首先將shouldComponentUpdate
置回false
,然後根據selector
中的計算過程是否存在error
,如果存在error
就丟擲,否則執行
createElement(WrappedComponent, this.addExtraProps(selector.props))複製程式碼
如果你對上面語句不太熟悉,其實上面程式碼等同於:
return (
<WrappedComponent
{...this.addExtraProps(selector.props)}
/>
)複製程式碼
其實所謂的jsx
也無非是createElement
語法糖,所有的jsx
的語法都會被編譯成React.createElement
,所以哪怕你的程式碼中沒有顯式的用到React
,只要有jsx
語法,就必須存在React
。
if (process.env.NODE_ENV !== 'production') {
Connect.prototype.componentWillUpdate = function componentWillUpdate() {
// We are hot reloading!
if (this.version !== version) {
this.version = version
this.initSelector()
if (this.subscription) this.subscription.tryUnsubscribe()
this.initSubscription()
if (shouldHandleStateChanges) this.subscription.trySubscribe()
}
}
}複製程式碼
React-Redux在生產環境下是不支援熱過載的,只有在開發環境下提供這個功能。在開發環境中,元件在will update
時會根據this.version
與version
去判斷,如果兩者不一樣,則初始化selector
,取消之前的訂閱並重新訂閱新的subscription
。
Provider
import { Component, Children } from 'react'
import PropTypes from 'prop-types'
import { storeShape, subscriptionShape } from '../utils/PropTypes'
import warning from '../utils/warning'
let didWarnAboutReceivingStore = false
function warnAboutReceivingStore() {
if (didWarnAboutReceivingStore) {
return
}
didWarnAboutReceivingStore = true
warning(
'<Provider> does not support changing `store` on the fly. ' +
'It is most likely that you see this error because you updated to ' +
'Redux 2.x and React Redux 2.x which no longer hot reload reducers ' +
'automatically. See https://github.com/reactjs/react-redux/releases/' +
'tag/v2.0.0 for the migration instructions.'
)
}
export function createProvider(storeKey = 'store', subKey) {
const subscriptionKey = subKey || `${storeKey}Subscription`
class Provider extends Component {
getChildContext() {
return { [storeKey]: this[storeKey], [subscriptionKey]: null }
}
constructor(props, context) {
super(props, context)
this[storeKey] = props.store;
}
render() {
return Children.only(this.props.children)
}
}
if (process.env.NODE_ENV !== 'production') {
Provider.prototype.componentWillReceiveProps = function (nextProps) {
if (this[storeKey] !== nextProps.store) {
warnAboutReceivingStore()
}
}
}
Provider.propTypes = {
store: storeShape.isRequired,
children: PropTypes.element.isRequired,
}
Provider.childContextTypes = {
[storeKey]: storeShape.isRequired,
[subscriptionKey]: subscriptionShape,
}
Provider.displayName = 'Provider'
return Provider
}
export default createProvider()複製程式碼
首先我們看看函式createProvider
,createProvider
函式的主要作用就是定製Provider
,我們知道Provider
的主要作用是使得其所有子元件可以通過context
訪問到Redux的store
。我們看到createProvider
返回了類Provider
,而類Provider
的getChildContext
函式返回了{ [storeKey]: this[storeKey], [subscriptionKey]: null }
,使得所有子元件都能訪問到store
。需要注意的是,要想使得子元件訪問到context
必須同時定義兩點:getChildContext
函式與static childContextTypes = {}
。並且我們知道Redux 2.x 與React-Redux 2.x不再支援熱過載的reducer
,所以在非生產環境下,我們會為Provider
新增生命週期函式componentWillReceiveProps
,如果store
的值發生了變化,就會在提供警告提示。
Provider
的render
函式中返回了Children.only(this.props.children)
。Children
是React
提供的處理元件中this.props.children
的工具包(utilities)返回僅有的一個子元素,否則(沒有子元素或超過一個子元素)報錯且不渲染任何東西。所以Provider
僅支援單個子元件。
最後歡迎大家關注我的掘金賬號或者部落格,不足之處,歡迎指正。