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...

相關文章
相關標籤/搜索