React-Redux 源码分析(三)-- connect

主要的核心在于connect,下面经过一个简化版的connect来讲下它的主要做用。react

在第一篇文章的时候说过,connect这个函数其实最终会返回一个包着渲染组件的高阶组件。git

它的基础做用以下:
一、从context里获取store
二、在componentWillMount 里经过mapStateToProps获取stateProp的值
三、在componentWillMount 里经过mapDispatchToProps获取dispatchProps的值
四、在componentWillMount 里订阅store的变化
五、将得到的stateProp,dispatchProps,还有自身的props合成一个props传给下面的组件github

export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
  class Connect extends Component {
    static contextTypes = {
      store: PropTypes.object
    }

    constructor () {
      super()
      this.state = {
        allProps: {}
      }
    }

    componentWillMount () {
      const { store } = this.context
      this._updateProps()
      store.subscribe(() => this._updateProps())
    }

    _updateProps () {
      const { store } = this.context
      let stateProps = mapStateToProps
        ? mapStateToProps(store.getState(), this.props)
        : {} // 防止 mapStateToProps 没有传入
      let dispatchProps = mapDispatchToProps
        ? mapDispatchToProps(store.dispatch, this.props)
        : {} // 防止 mapDispatchToProps 没有传入
      this.setState({
        allProps: {
          ...stateProps,
          ...dispatchProps,
          ...this.props
        }
      })
    }

    render () {
      return <WrappedComponent {...this.state.allProps} />
    }
  }
  return Connect
}

明白了上面以后,咱们就能够来看真的connect长啥样了。redux

connect接受四个参数:mapStateToProps,mapDispatchToProps,mergeProps,optiponssegmentfault

返回:一个注入了 state 和 action creator 的 React 组件缓存

具体的mapStateToProps,mapDispatchToProps,mergeProps,optipons如何使用能够看官方文档,
这里简单的说下~性能优化

mapStateToProps

传入:state,ownProps
输出:statePropsapp

这个很是关键,若是定义了这个参数,就会监听redux store的变化,没有的话,就不会。该回调函数必须返回一个纯对象,这个对象会与组件的 props 合并。
同时,若是指定了第二个ownProps,这个参数的值为传入到组件的props,只要组件接受到新的props,mapStateToProps也会被调用ide

mergeProps(function)

stateProps,dispatchProps,自身的props将传入到这个函数中。
默认是Object.assign({}, ownProps, stateProps, dispatchProps)函数

options(Object)

pure = true ; // 默认值,这时候connector 将执行 shouldComponentUpdate 而且浅对比 mergeProps 的结果,避免没必要要的更新。因此其实connect是有帮咱们作性能优化的。

最终使用:

connect(mapStateToProps, mapDispatchToProps, mergeProps)(MyComponent)

啰嗦了那么多,看看Connect的源码的目录结构。

clipboard.png

connect.js

connect.js 就是将相关的参数传给connectAdvanced.js

其中有个参数:

// if mapStateToProps is falsy, the Connect component doesn't subscribe to store state changes
// 因此,若是mapStateToProps没有传的话,是不订阅store更新的
shouldHandleStateChanges: Boolean(mapStateToProps),

connectAdvanced

重点看看connectAdvanced

其中有个比较重要的概念,selectorFactory 它的做用是负责运行选择器函数,这些函数从state,props,dispatch里计算出新的props。

例如:

export default connectAdvanced((dispatch, options) => (state, props) => ({
    thing: state.things[props.thingId],
    saveThing: fields => dispatch(actionCreators.saveThing(props.thingId, fields)),
  }))(YourComponent)

selectorFactory

当pure为true的时候,返回的selector就会缓存它们各自的结果,这样connectAdvance里的shouldComponentUpdate就能够对比this.props和nextProps,当没有发生改变的时候返回false
不更新。当pure为false的时候,shouldComponentUpdate在任什么时候候都会返回true。

const selectorFactory = options.pure
? pureFinalPropsSelectorFactory
: impureFinalPropsSelectorFactory

impureFinalPropsSelectorFactory

因为impure每次都会返回新的object,因此每一次shouldComponentUpdate对比的时候都必定会返回true

export function impureFinalPropsSelectorFactory(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
  dispatch
) {
  return function impureFinalPropsSelector(state, ownProps) {
    return mergeProps(
      mapStateToProps(state, ownProps),
      mapDispatchToProps(dispatch, ownProps),
      ownProps
    )
  }
}

pureFinalPropsSelectorFactory

而pureFinalPropsSelectorFactory 会缓存上一次stateProps, dispatchProps,mergeProps,当须要更新的时候,当那哪一部分须要更新的时候才更新

export function pureFinalPropsSelectorFactory(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
  dispatch,
  { areStatesEqual, areOwnPropsEqual, areStatePropsEqual }
) {
  let hasRunAtLeastOnce = false
  let state
  let ownProps
  let stateProps
  let dispatchProps
  let mergedProps
 // 第一次跑的时候,就去得到mergedProps,而且将hasRunAtLeastOnce 设置成true,
    下次跑的时候,就直接走handleSubsequentCalls
  function handleFirstCall(firstState, firstOwnProps) {
    state = firstState
    ownProps = firstOwnProps
    stateProps = mapStateToProps(state, ownProps)
    dispatchProps = mapDispatchToProps(dispatch, ownProps)
    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
    hasRunAtLeastOnce = true
    return mergedProps
  }
  // 若是props state都变化的时候,mapStateToProps是必定会变化的,
  // 而mapDispatchToProps会看ownProps是否存在
  function handleNewPropsAndNewState() {
    stateProps = mapStateToProps(state, ownProps)

    if (mapDispatchToProps.dependsOnOwnProps)
      dispatchProps = mapDispatchToProps(dispatch, ownProps)

    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
    return mergedProps
  }

  function handleNewProps() {
    if (mapStateToProps.dependsOnOwnProps)
      stateProps = mapStateToProps(state, ownProps)

    if (mapDispatchToProps.dependsOnOwnProps)
      dispatchProps = mapDispatchToProps(dispatch, ownProps)

    mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
    return mergedProps
  }
  // 若是只是state改变的话,就只须要看mapStateToProps,而后他们还会比较先后
  // 是否相等,若是不相等,就合并
  function handleNewState() {
    const nextStateProps = mapStateToProps(state, ownProps)
    const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps)
    stateProps = nextStateProps
    
    if (statePropsChanged)
      mergedProps = mergeProps(stateProps, dispatchProps, ownProps)

    return mergedProps
  }
  
  // 不是第二次的时候,走这个函数
  function handleSubsequentCalls(nextState, nextOwnProps) {
    // 首先对比nextOwnProps跟ownProps是否相等
    const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps)
    // 对比nextState,state是否相等
    const stateChanged = !areStatesEqual(nextState, state)
    // 比较完成赋值,下一次对比时候使用
    state = nextState
    ownProps = nextOwnProps
    // 当props跟state都变化的时候,看handleNewPropsAndNewState
    if (propsChanged && stateChanged) return handleNewPropsAndNewState()
    if (propsChanged) return handleNewProps()
    if (stateChanged) return handleNewState()
    // 注意 若是state跟props都没有发生改变的话,直接返回以前的mergedProps,
    // 组件不从新渲染
    return mergedProps
  }

  return function pureFinalPropsSelector(nextState, nextOwnProps) {
    return hasRunAtLeastOnce
      ? handleSubsequentCalls(nextState, nextOwnProps)
      : handleFirstCall(nextState, nextOwnProps)
  }
}

总结来讲,当state变化的时候,mapStateToProps必定会被调用,而props变化的时候要看dependsOnOwnProps,计算出来以后,用mergProps更新。

更新以后的props在哪里进行比较呢?

上面说的,其实都依赖于connect里面的代码帮忙,connect在contructor里初始化selector

initSelector() {
    const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
    this.selector = makeSelectorStateful(sourceSelector, this.store)
    this.selector.run(this.props)
 }


function makeSelectorStateful(sourceSelector, store) {
  // wrap the selector in an object that tracks its results between runs.
  const selector = {
    // run其实作了两个事情
    //一、去计算nextProps的值
    //二、若是有更新,设置shouldComponentUpdate=true 跟 props
    run: function runComponentSelector(props) {
      try {
        const nextProps = sourceSelector(store.getState(), props)
        // 注意这里只是进行了一个浅比较,当不等的时候,shouldComponentUpdate = true
        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
}

接下来 生命周期的话仍是看react-redux里的写法吧~

componentDidMount() {
    //shouldHandleStateChanges 默认为true
    if (!shouldHandleStateChanges) return

    // componentWillMount fires during server side rendering, but componentDidMount and
    // componentWillUnmount do not. Because of this, trySubscribe happens during ...didMount.
    // Otherwise, unsubscription would never take place during SSR, causing a memory leak.
    // To handle the case where a child component may have triggered a state change by
    // dispatching an action in its componentWillMount, we have to re-run the select and maybe
    // re-render.
    // 意思就是SSR的时候会触发componentWillMount ,可是不会触发componentDidMount ,
    // componentWillUnmount,因此订阅事件要放在componentDidMount里面,不然, 就没办法取消订阅
    // 形成内存泄漏。
    
    this.subscription.trySubscribe()
    this.selector.run(this.props)
    // 当shouldComponentUpdate= true的时候,强制刷新
    if (this.selector.shouldComponentUpdate) this.forceUpdate()
  }

  componentWillReceiveProps(nextProps) {
    // 前面说过,this.selector.run有两点做用:
    // 1 计算下一个props
    // 2 经过对比看看shouldComponentUpdate的值
    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
  }

最关键的是,onStateChange,当咱们订阅了这个函数的时候,每一次dispatch都会触发它,

onStateChange() {
   // 依旧是先经过selector获取nextProps的值
   this.selector.run(this.props)
    // 而后若是shouldComponentUpdate=true
    if (!this.selector.shouldComponentUpdate) {
        // 这个是啥意思?是这样的,每一个Connect组件里面都有一个subscription对象,它也是一个订阅模型
        // 每一个父的Connect订阅的是 子Connect组件的onStateChange函数,而父的Connect的onStateChange
        // 函数,被谁订阅呢?固然是store(redux)啦, 即流程为
       // dispathc(action)---触发store的订阅即父的onStateChange---父的onStateChange触发即触发子
       // Connect的onStateChange
      this.notifyNestedSubs()
    } else {
      this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate
      this.setState(dummyState)
    }
}

最后在render的时候

render() {
    const selector = this.selector
    selector.shouldComponentUpdate = false

    if (selector.error) {
      throw selector.error
    } else {
      // 将mege的props传给wrappedComponent
      return createElement(WrappedComponent, this.addExtraProps(selector.props))
    }
  }

最终

return hoistStatics(Connect, WrappedComponent)

想一想hoistStatics是什么做用,它实际上就是相似Object.assign将子组件中的 static 方法复制进父组件,但不会覆盖组件中的关键字方法(如 componentDidMount)。

好长啊我都有点累了。真是枯燥无聊又无趣,仍是用一个图代表一下connect大体的流程吧。

clipboard.png

说了那么多,其实只要记住亮点就能够了:
一、connect是一个函数,会返回一个connect的类,里面包着咱们要渲染的wrappedComponent,而后将stateProps,dispatchProps,还有ownProps合并起来,一块儿传给wrappedComponent
二、connect其实帮咱们作了性能的优化的,当state跟props发生改变时,selector若是变化,最终计算出来的结果会进行一次浅比较来设置shouldComponentUpdate防止重复渲染

参考:
一、https://segmentfault.com/a/11...
二、https://github.com/reactjs/re...

相关文章
相关标签/搜索