react-redux 源码解析

2,093 阅读7分钟
原文链接: github.com

本文所有文件位于react-redux-doc中。

之前说过,react-redux有些复杂,其实react-redux只有两个核心概念,Provider和connect。Provider的源码不是傻子都能轻松阅读(不严谨,别较真~),唯一有点难度的,就是connect了。

Provider

provider用来给react组件提供数据,数据放在context中的store键。源码非常简单。

function createProvider(storeKey = 'store', subKey) {
    const subscriptionKey = subKey || `${storeKey}Subscription`

    class Provider extends Component {
        // getChildContext: 将store传递给子孙component
        getChildContext() {
          return { [storeKey]: this[storeKey], [subscriptionKey]: null }
        }

        // 拿到传入的store直接挂在到当前store上
        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) {
        // 如果store有改变,则发出警告
        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
}

我们看到,仅仅是把传入的store放在this[storeKey],又在getChildContext中把这个store返回给子组件而已。这里为了可扩展,storeKey当成参数(默认值store)传递进去。此外,子组件还能在context中获得另外一个值:key = storeSubscription ,值为null的属性。目前并没有什么卵用。

另外,每次改变store属性时候,都会报出一个警告。第二次改变时候不会警告,直接退出。也就是说Provider不支持动态修改store。

connect

connect通过传入mapStateToProps, mapDispatchToProps,mergeProps, options。动态计算出应该传递给react组件具体的哪些属性。

const FinalApp = connect(select, mapDispatchToProps)(App)

这是一个柯里化函数。第一级传入刚才4个参数,第二层传入要连接的lianreact组件。(示例链接)

我们依然先看看connect的函数签名

function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {})

mapStateToProps是一个函数,接受state并且返回一个对象,对象包括要传递给组件的属性,这里我们可以对state做筛选与过滤。因为我们只需要全局数据的部分数据传递给组件。

如:

function mapStateToProps(state) {
  return {
    visibleTodos: selectTodos(state.todos, state.visibilityFilter),
    visibilityFilter: state.visibilityFilter
  }
}

mapDispatchToProps同样也是一个函数,返回要传递给组件的函数对象。

// actions.js
export function addTodo(text) {
  return { type: ADD_TODO, text }
}

export function completeTodo(index) {
  return { type: COMPLETE_TODO, index }
}

export function setVisibilityFilter(filter) {
  return { type: SET_VISIBILITY_FILTER, filter }
}

export default {
  addTodo, completeTodo, setVisibilityFilter,
}

// App.js
function mapDispatchToProps(dispatch) {
  return { actions: bindActionCreators(actions, dispatch) }
}

这样我们就把addTodo, completeTodo, setVisibilityFilter当做事件传递给组件,组件内使用事件时,只需要actions.addTodo即可。这么做的好处是,第一,组件只拿到了自己关心的事件,而不用担心被全局污染。第二,组件不用感知redux的存在,不需要dispatch,只需要当做普通事件使用即可。第三,事件与组件完全分离,既减少了代码量,又做到了高可维护性。

mergeProps还是一个函数,用来筛选哪些参数传递给组件。这个函数接受3个参数。

const defaultMergeProps = (stateProps, dispatchProps, parentProps) => ({
  ...parentProps,
  ...stateProps,
  ...dispatchProps
})

stateProps是mapStateToProps的返回值,dispatchProps是mapDispatchToProps返回值,parentProps是当前组件自己的属性。这个函数默认把这三个返回值拼装到一起传递给组件。当然,我们也可以随意做修改过滤等操作。

最后一个是options,这个是一个对象,有两个布尔,一个是pure,一个是withRef。 如果pure为false,只要connect接受到属性,不管是否有变化,必然刷新connect组件。withRef为true时,在装饰传入的 React 组件时,Connect 会保存一个对该组件的 refs 引用,可以通过 getWrappedInstance 方法来获得该 refs,并最终获得原始的 DOM 节点。

我们看下源码实现

function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
  // 是否应该订阅,根据传入的map决定,事实上,基本都会订阅
  const shouldSubscribe = Boolean(mapStateToProps)

  // mapState: 传入的函数或者空函数
  const mapState = mapStateToProps || defaultMapStateToProps

  // mapState: 传入的函数或者空函数或者包装函数
  let mapDispatch
  if (typeof mapDispatchToProps === 'function') {
    mapDispatch = mapDispatchToProps
  } else if (!mapDispatchToProps) {
    mapDispatch = defaultMapDispatchToProps
  } else {
    mapDispatch = wrapActionCreators(mapDispatchToProps)
  }

  // mergeProps 参数也是一个函数,接受 stateProps、dispatchProps 和ownProps 作为参数。
  // 实际上, stateProps 就是我们传给 connect 的第一个参数 mapStateToProps 最终返回的 props。同理,
  // dispatchProps 是第二个参数的最终产物,而 ownProps 则是组件自己的 props。
  // 这个方法更大程度上只是为了方便对三种来源的 props 进行更好的分类、命名和重组。
  const finalMergeProps = mergeProps || defaultMergeProps


  // pure配置为 false 时,connect组件接受到属性时,必然刷新。
  // withRef布尔值,默认为 false。如果设置为 true,在装饰传入的 React 组件时,Connect 会保存一个对该组件的 refs 引用,
  // 你可以通过 getWrappedInstance 方法来获得该 refs,并最终获得原始的 DOM 节点。
  const { pure = true, withRef = false } = options
  const checkMergedEquals = pure && finalMergeProps !== defaultMergeProps

  // Helps track hot reloading.
  const version = nextVersion++

一级柯里化主要对默认值做了一些处理,defaultMapStateToProps是个空函数,也就是默认不会往react传redux数据的任何属性。而defaultMapDispatchToProps只会往react传递一个属性,就是dispatch,另外mapDispatchToProps也可以传入actionCreator。defaultMergeProps刚才说过,就是把mapStateToProps与mapDispatchToProps和组件自己的属性合并传递给组件。

接着,计算一个叫做checkMergedEquals的变量,如果pure并且传入了mergeProps函数(这块为什么加mergeProps判断我有疑问),那么组件就不需要重新渲染。

接下来我们看下二级柯里化代码

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 {

      // 生命周期函数
      constructor(props, context) {
        super(props, context)
        this.version = version
        // Provider提供的store
        this.store = props.store || context.store

        // 如果没有传入store并且也没有Provider包装,提出提示
        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}".`
        )

        // 获取store的state
        const storeState = this.store.getState()
        // 把store的state作为组件的state,后面通过更新state更新组件
        this.state = { storeState }
        // 清空缓存值,这里为初始化this信息
        this.clearCache()
      }

      shouldComponentUpdate() {
        // 这里通常都是hasStoreStateChanged = true, 其他两项很少生效
        return !pure || this.haveOwnPropsChanged || this.hasStoreStateChanged
      }

      componentDidMount() {
        this.trySubscribe()
      }

      // 接受参数,connect属性(目前只有store),很少能用到,因为都是用Provider传递store的
      componentWillReceiveProps(nextProps) {
        if (!pure || !shallowEqual(nextProps, this.props)) {
          this.haveOwnPropsChanged = true
        }
      }

      // 组件卸载时候取消订阅,并且清空缓存
      componentWillUnmount() {
        this.tryUnsubscribe()
        this.clearCache()
      }

      isSubscribed() {
        return typeof this.unsubscribe === 'function'
      }

      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
        }

        // 标记store发生变化
        this.hasStoreStateChanged = true
        // 重新改变state,也就重新触发render
        this.setState({ storeState })
      }

      /* 订阅函数, didUpdate调用 */
      trySubscribe() {
        if (shouldSubscribe && !this.unsubscribe) {
          // store订阅this.handleChange
          this.unsubscribe = this.store.subscribe(this.handleChange.bind(this))
          this.handleChange()
        }
      }

      /* 取消订阅函数, willUnMount调用 */
      tryUnsubscribe() {
        if (this.unsubscribe) {
          this.unsubscribe()
          this.unsubscribe = null
        }
      }

      /* 清空缓存信息, 加载,卸载以及connect属性变化时候触发,connect属性通常不会变化 */
      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
      }

      // 这个逻辑和计算state相同
      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
      }

      // 深度比较 props是否有变化
      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
      }

      // 获得组件当前的state(经过mapPropsToState)的值
      configureFinalMapState(store, props) {
        // mapState是当前组件的mapPropsToState的函数, mappedState是函数的计算结果,也就是当前组件state
        const mappedState = mapState(store.getState(), props)
        const isFactory = typeof mappedState === 'function'

        // 缓存mapStateToProps,如果返回的是函数,就用返回值再当mapStateToProps
        this.finalMapStateToProps = isFactory ? mappedState : mapState

        // 如果参数的长度为不为1,那么依赖于props
        this.doStatePropsDependOnOwnProps = this.finalMapStateToProps.length !== 1

        if (isFactory) {    // 如果返回的是函数,返回computeStateProps再计算值
          return this.computeStateProps(store, props)
        }

        if (process.env.NODE_ENV !== 'production') {
          checkStateShape(mappedState, 'mapStateToProps')
        }

        // 返回map后的state
        return mappedState
      }

      // 深度比较 props是否有变化
      computeStateProps(store, props) {
        // 如果不是第一次计算,从缓存中读取mapPropsToState
        if (!this.finalMapStateToProps) {
          return this.configureFinalMapState(store, props)
        }

        const state = store.getState()

        // 判断mapPropsToState是否依赖自己的属性,如果有,传递自己的属性执行函数
        const stateProps = this.doStatePropsDependOnOwnProps ?
          this.finalMapStateToProps(state, props) :
          this.finalMapStateToProps(state)

        if (process.env.NODE_ENV !== 'production') {
          // 如果stateProps格式不符合要求给出提示
          checkStateShape(stateProps, 'mapStateToProps')
        }
        return stateProps
      }

      // 深度比较 props是否有变化
      updateStatePropsIfNeeded() {
        const nextStateProps = this.computeStateProps(this.store, this.props)
        if (this.stateProps && shallowEqual(nextStateProps, this.stateProps)) {
          return false
        }

        // 更新stateProps为下一个计算后的state
        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
      }

      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

        // 判断是否应该更新state与dispatch的属性
        if (pure && renderedElement) {
          // 如果组件自己属性变化,state变化并且经过浅对比 || 组件自己的属性变化并且mapPropsToState依赖自己的属性
          shouldUpdateStateProps = hasStoreStateChanged || (
            haveOwnPropsChanged && this.doStatePropsDependOnOwnProps
          )

          // 如果组件自己属性变化,并且传入的mapDispatchToProps有两个参数时,会重新触发计算
          shouldUpdateDispatchProps =
            haveOwnPropsChanged && this.doDispatchPropsDependOnOwnProps
        }

        let haveStatePropsChanged = false
        let haveDispatchPropsChanged = false

        // 如果已经预计算,那组store的state肯定发生过变化,详见 handleChange
        if (haveStatePropsBeenPrecalculated) {
          haveStatePropsChanged = true
        } else if (shouldUpdateStateProps) {          // 如果没有预计算,重新计算
          haveStatePropsChanged = this.updateStatePropsIfNeeded()
        }

        // 是否应该重新计算dispatch props
        if (shouldUpdateDispatchProps) {
          haveDispatchPropsChanged = this.updateDispatchPropsIfNeeded()
        }

        let haveMergedPropsChanged = true

        // 如果属性变化,dispatch属性变化或者组件自己的属性变化,任一一个都可能触发重新渲染
        if (
          haveStatePropsChanged ||
          haveDispatchPropsChanged ||
          haveOwnPropsChanged
        ) {
          // 计算最终的mergeProps,并且返回是否需要更新组件
          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

    // 从context中获取Provider放的store
    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)
  }

二级柯里化代码非常长,而且杂乱无章,看的我头疼(亲爱的读者你看到的是我重新排版后的connect顺序,已经非常非常清晰,如果你还是看不懂的话,请考虑换个姿势看)。

从生命周期开始,分布解说:

  • constructor 我们可以看到this.store = context.store,这里拿到了Provider提供的store。赋给了state的storeState属性,所以后面通过更新state更新组件,并且清空了缓存信息,这里为了初始化this信息。

  • componentDidMount 这里执行订阅,如果传入了mapStateToProps事件,并且没有取消订阅(组件卸载时候执行),那就执行订阅函数。订阅了this.handleChange,每当数据有变化时候,会自动执行这个订阅函数。并且初始化时候也执行了this.handleChange。

    那么这个handleChange做了什么呢?

    • 首先判断是否取消订阅 或者 当前状态和上次状态是否相同。如果是的话,返回。
    • 我们拿当前状态和上次状态做浅对比,如果没有变化,返回。
    • 如果比较出错,记录错误。
    • 记录预计算标示与storeState变化标示(稍后会用到)
    • 重新改变state,也就重新触发render
  • componentWillReceiveProps 如果pure为false 或者 组件自己的属性与上次属性做浅对比,如果发生变化,把this.haveOwnPropsChanged设置成true,为了shouldComponentUpdate使用

  • shouldComponentUpdate 如果pure为false 或者 组件自己的属性有变化 或者 storeState有变化,返回true,更新组件。大家注意下,dispatch的事件发生变化时候,并不会刷新组件。

  • componentWillUnmount 取消订阅this.handleChange,并且清空this上的属性。

  • render

    • 如果之前handleChange预计算发生错误,直接抛出
    • 然后,根据条件(如果pure = true,并且不是第一次渲染),判断是否再次计算state属性与计算事件属性。
      • 是否需要再次计算state属性:如果组件storeState发生变化 || 组件自己的属性变化并且mapPropsToState依赖自己的属性
      • 是否计算事件属性,事件属性依赖自己的属性
    • 接着声明两个变量记录stateProps与dispatchProps是否需要更新
      • stateProps是否需要更新:如果重计算标示为true,则有变化。如果为false,调用updateStatePropsIfNeeded方法来判断属性是否有变化,这个方法执行了最先传入的mapStateToProps函数,并把结果返回的组件属性与之前的storeState做了浅对比。如果为true,则stateProps需要更新。
      • dispatchProps是否需要更新:同上,原理差不多,注释很清楚,自己看实现吧。
    • 如果 stateProps 或者 dispatchProps 或者组件自己的属性任意一个需要更新则更新组件,否则返回缓存的组件也就是上次渲染的connect组件。
    • 渲染更新组件时候,传入this.mergedProps,也就是之前多次强调的三者合成属性(stateProps,dispatchProps与ownProps)。如果withRef = true,则还传入了ref为wrappedInstance的属性。
    • 接着我们在Connect组件上赋了displayName和(展示名称,也就是组件类型)和WrappedComponent(被包装组件类)。最后返回的时候用hoistStatics将react组件中的所有属性拷贝到Connect组件中。