庖丁解牛React-Redux(一): connectAdvanced

  轉眼間2017年已通過了一半了,看到以前有人問是否完成了本身半年的計劃,答案是:固然沒有啦。感受本身如今對技術產生了敬畏,由於要學習的知識是在是太多了,而本身的時間和精力卻很難達到目標,目前處在比較焦慮的狀態。本身是年初進入掘金的,半年內雖然文章的閱讀量不錯可是關注度過低,半年就混了40個關注,說來真是慚愧。
  
  扯遠了,咱們言歸正傳,上次的文章Redux:百行代碼千行文檔解釋了Redux內部的運做原理。可是咱們在React中不多會直接搭配使用Redux,而是經過React-Redux綁定React與Redux。這篇文章咱們咱們將瞭解React-Redux其中的奧祕。在閱讀以前但願你有React-Redux的使用經驗,不然這篇文章可能不太適合你。
  
  首先咱們能夠看看React-Redux的源碼目錄結構,大體看一下,作到內心有數。javascript

.
├── components
│   ├── Provider.js
│   └── connectAdvanced.js
├── connect
│   ├── connect.js
│   ├── mapDispatchToProps.js
│   ├── mapStateToProps.js
│   ├── mergeProps.js
│   ├── selectorFactory.js
│   ├── verifySubselectors.js
│   └── wrapMapToProps.js
├── index.js
└── utilsjava

├── PropTypes.js
├── Subscription.js
├── shallowEqual.js
├── verifyPlainObject.js
├── warning.js
└── wrapActionCreators.js

  首先來看一下index.js:react

import Provider, { createProvider } from './components/Provider'
import connectAdvanced from './components/connectAdvanced'
import connect from './connect/connect'

export { Provider, createProvider, connectAdvanced, connect }

  咱們能夠看出來,React-Redux對外提供的API有四個:ProvidercreateProvider,connectAdvanced,connect。咱們將從connectAdvanced開始介紹。git

connectAdvanced

  其實我在看React-Redux源碼以前都不知道有這個API,爲了方便後面的源碼理解,咱們介紹一下connectAdvanced:
  
connectAdvanced(selectorFactory, [connectOptions])github

  connectAdvanced用來鏈接組件到Redux的store上。是connect函數的基礎,但並無規定如何將statepropsdispatch處理傳入最終的props中。connectAdvanced並無對產生的props作緩存來優化性能,都留給了調用者去實現。connectAdvanced並無修改傳入的組件類,而是返回一個新的、鏈接到store的組件類。redux

參數:緩存

  • 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屬性。app

返回:

  函數返回一個高階組件,該高階組件將從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)
  }
}

  函數接受兩個參數:selectorFactoryconnectOptions(可選),返回一個高階組件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中定義了subscriptionKeyversion以及爲Connect組件定義的contextTypeschildContextTypes(不瞭解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()
}

  首先咱們先看看用來初始化selectorinitSelector函數:

//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中的stateownProps中計算新的props,並返回純對象(plain object),這個對象會做爲props傳遞給被包裹的組件(WrappedComponent)。在initSelector中,首先調用selectorFactory從而初始化sourceSelector,咱們並不會直接調用sourceSelector,而是爲了程序的健壯,經過將sourceSelector做爲參數調用makeSelectorStateful,返回更加安全的selector。今後以後,咱們想要生成新的props只須要調用selector.run函數。在selector.run函數中對sourceSelector的異常作了處理,並用sourceSelector.error記錄是否存在異常。sourceSelector.shouldComponentUpdate用來根據先後兩次返回的props是否相同,從而記錄是否應該刷新組件,這就爲後期的性能提高留出了空間,只要在先後數據相同時,咱們就返回同一個對象,使得shouldComponentUpdatefalse,就能夠避免沒必要要的刷新,固然這不是咱們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經過閉包的方式存儲currentnext,而後返回

{
    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。例如你設置了storeKeyotherStore,那麼就能夠經過給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的方式傳入的,那麼parentSubthis.context[subscriptionKey]。若是是以props的方式傳入的,若顯式地給組件以props的方式傳入subscription時,parentSub值爲this.props.subscription。須要注意的是,咱們在initSubscription中拷貝了當前this.subscription中的notifyNestedSubs方法,目的是防止在notify循環過程當中組件卸載,使得this.subscriptionnull。咱們在組件卸載時,會將值賦值爲一個名爲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選擇是否訂閱storestate改變。組件在接受props時,會使用selector計算新的props並執行相應的聲明週期。shouldComponentUpdate會根據this.selector存儲的值shouldComponentUpdate來判斷是否須要刷新組件。在組件will mount時會作相應的清理,防止內存泄露。

  接着咱們介紹其餘的類方法:

getWrappedInstance() {
    return this.wrappedInstance
}

setWrappedInstance(ref) {
    this.wrappedInstance = ref
}

  getWrappedInstancesetWrappedInstance在可選參數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的結果中shouldComponentUpdatefalse,表示不須要刷新當前組件僅須要通知子組件更新。若是shouldComponentUpdatetrue,會經過設置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屬性用來綁定回調存儲組件實例的函數setWrappedInstancerenderCountProp爲當前組件屬性刷新的次數,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.versionversion去判斷,若是二者不同,則初始化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,而類ProvidergetChildContext函數返回了{ [storeKey]: this[storeKey], [subscriptionKey]: null },使得全部子組件都能訪問到store。須要注意的是,要想使得子組件訪問到context必須同時定義兩點:getChildContext函數與static childContextTypes = {} 。而且咱們知道Redux 2.x 與React-Redux 2.x再也不支持熱重載的reducer,因此在非生產環境下,咱們會爲Provider添加生命週期函數componentWillReceiveProps,若是store的值發生了變化,就會在提供警告提示。
  Providerrender函數中返回了Children.only(this.props.children)ChildrenReact提供的處理組件中this.props.children的工具包(utilities)返回僅有的一個子元素,不然(沒有子元素或超過一個子元素)報錯且不渲染任何東西。因此Provider僅支持單個子組件。
  
  最後歡迎你們關注個人掘金帳號或者博客,不足之處,歡迎指正。

相關文章
相關標籤/搜索