Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
MrErHu opened this issue Jul 9, 2017 · 0 comments
Open

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

MrErHu opened this issue Jul 9, 2017 · 0 comments

Comments

@MrErHu
Copy link
Owner

MrErHu commented Jul 9, 2017

  转眼间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有四个:ProvidercreateProvider,connectAdvanced,connect。我们将从connectAdvanced开始介绍。

connectAdvanced

  其实我在看React-Redux源码之前都不知道有这个API,为了方便后面的源码理解,我们介绍一下connectAdvanced:
  
connectAdvanced(selectorFactory, [connectOptions])

  connectAdvanced用来连接组件到Redux的store上。是connect函数的基础,但并没有规定如何将statepropsdispatch处理传入最终的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)
  }
}

  函数接受两个参数: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仅支持单个子组件。
  
  最后欢迎大家关注我的掘金账号或者博客,不足之处,欢迎指正。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant