庖丁解牛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
└── utils
├── PropTypes.js
├── Subscription.js
├── shallowEqual.js
├── verifyPlainObject.js
├── warning.js
└── wrapActionCreators.jsjava

  首先来看一下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属性。

返回:安全

  函数返回一个高阶组件,该高阶组件将从store的state中构建的props传递给被包裹组件。闭包

例如:app

// 按照用户信息选择性传入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仅支持单个子组件。
  
  最后欢迎你们关注个人掘金帐号或者博客,不足之处,欢迎指正。

相关文章
相关标签/搜索