1. 程式人生 > >React-redux原理探索

React-redux原理探索

提示 about IE out req message == 通知 sed

先看一段react-redux代碼再探索原理

import React from ‘react‘;
import { render } from ‘react-dom‘;
import { createStore } from ‘redux‘;
import { Provider } from ‘react-redux‘;
import routes from ‘./router‘;
import reducer from ‘./reducer‘;

const store = createStore(
  reducer
);

render(
  <Provider store={store}>
    {routes}
  
</Provider>, document.getElementById(‘root‘) );

先調用redux的createStore生成store,再將store作為屬性傳給Provider組件。

現在看看react-redux源代碼。

index.js頁暴露了以下幾個接口

export { Provider, createProvider, connectAdvanced, connect }

上面的demo使用的是Provider組件,Provider的主要作用是使得其所有子組件可以通過context訪問到Redux的store,現在深入了解一下Provider組件的實現原理。

  class Provider extends Component {
    // 將store放入context使子孫組件能夠訪問到store
    getChildContext() {
      return { [storeKey]: this[storeKey], [subscriptionKey]: null }
    }

    constructor(props, context) {
      super(props, context)
      // 獲取redux實例
      this[storeKey] = props.store;
    }

    render() {
      
// 使整個應用成為Provider的子組件 // 確保Provider組件的直接子級為單個封閉元素,切勿多個組件平行放置。 return Children.only(this.props.children) } } // Redux 2.x 與React-Redux 2.x不再支持熱重載的reducer,所以在非生產環境下, // 我們會為Provider添加生命周期函數componentWillReceiveProps,如果store的值發生了變化,就會在提供警告提示 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, } return Provider }

connect的作用就是將state和dispatch綁定到react組件中,使得組件可以訪問到redux,下面為connect使用demo

export default connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {})(ReactComponent)

connect源代碼比較長,把connect的核心實現簡化提取出來是下面的形式,最終返回的是把state和dispatch綁定到Connect的組件。

funtion connect(mapStateToProps,mapDispatchToProps,mergeProps,{一堆props}) {
return function wrapWithConnect(WrappedComponent) {
        class Connect extends Component {
        
        }
        return hoistStatics(Connect, WrappedComponent)
    }
}

其中hoistStatics(Connect, WrappedComponent)是自動把所有綁定在WrappedComponent對象上的非React方法都綁定到Connect上。

先看看傳入的四個參數mapStateToProps,mapDispatchToProps,mergeProps,{一堆props}作用

    const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, ‘mapStateToProps‘)
    const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, ‘mapDispatchToProps‘)
    const initMergeProps = match(mergeProps, mergePropsFactories, ‘mergeProps‘)

    return connectHOC(selectorFactory, {
      // used in error messages
      methodName: ‘connect‘,

       // used to compute Connect‘s displayName from the wrapped component‘s displayName.
      getDisplayName: name => `Connect(${name})`,

      // if mapStateToProps is falsy, the Connect component doesn‘t subscribe to store state changes
      shouldHandleStateChanges: Boolean(mapStateToProps),

      // passed through to selectorFactory
      initMapStateToProps,
      initMapDispatchToProps,
      initMergeProps,
      pure,
      areStatesEqual,
      areOwnPropsEqual,
      areStatePropsEqual,
      areMergedPropsEqual,

      // any extra options args can override defaults of connect or connectAdvanced
      ...extraOptions
    })

通過match方法生成新的函數傳遞給connectHOC(生成Connect組件的方法)

initMapStateToProps分兩種情況,不傳或者傳參為null時返回一個函數(運行傳參state返回空對象),傳參為函數時返回一個函數(執行後返回state中所傳入的屬性,例如下邊的函數會返回{todo:[]})
const mapStateToProps = state => ({
  todos: []
})
initMapDispatchToProps分為三種:
  • 不傳參時返回函數(函數運行參數傳入dispatch,返回對象{dispatch:function dispatch(){xxx}});
  • 傳參為函數時返回函數(函數運行傳參dispatch返回對象,例如下邊函數會返回{onClick:function(){xxx}})
const mapDispatchToProps = (dispatch, ownProps) => ({
  onClick: () => dispatch(setVisibilityFilter(ownProps.filter))
})
  • 傳參為對象時返回函數(函數運行傳參dispatch,返回{xxx:function(){xxx}})

initMergeProps不傳參默認返回 { ...ownProps, ...stateProps, ...dispatchProps }將所有props全部合成一個對象

這三種參數全部初始化完畢後執行connectHOC生成Connect組件,Connect組件會初始化兩樣東西

  • initSelector:selector的主要作用是通過執行initMapStateToProps,initMapDispatchToProps,initMergeProps生成的函數計算新的props,並返回純對象(plain object),將這個對象作為props傳遞給被包裹的組件(WrappedComponent)
      initSelector() {
        // 首先調用selectorFactory從而初始化sourceSelector,我們並不會直接調用sourceSelector,而是為了程序的健壯,
        // 通過將sourceSelector作為參數調用makeSelectorStateful,返回更加安全的selector。
        // 從此之後,我們想要生成新的props只需要調用selector.run函數。
        // 在selector.run函數中對sourceSelector的異常做了處理,並用sourceSelector.error記錄是否存在異常
        // function pureFinalPropsSelector(nextState, nextOwnProps) {
        //   return hasRunAtLeastOnce
        //     ? handleSubsequentCalls(nextState, nextOwnProps)
        //     : handleFirstCall(nextState, nextOwnProps)
        // }
        const sourceSelector = selectorFactory(this.store.dispatch, selectorFactoryOptions)
        this.selector = makeSelectorStateful(sourceSelector, this.store)
        this.selector.run(this.props)
      }

通過執行selector.run設置selector.shouldComponentUpdate再通過shouldComponentUpdate判斷selector.shouldComponentUpdate通知組件是否刷新

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

  shouldComponentUpdate() {
    return this.selector.shouldComponentUpdate
  }
  • initSubscription為store增加監聽事件,在store數據變化時執行onStateChange函數
      initSubscription() {
        if (!shouldHandleStateChanges) return

        // parentSub‘s source should match where store came from: props vs. context. A component
        // connected to the store via props shouldn‘t use subscription from context, or vice versa.
        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函數是store發生改變的回調函數,當回調onStateChange方法時,會通過selector計算新的props,如果計算selcetor的結果中shouldComponentUpdatefalse,表示不需要刷新當前組件僅需要通知子組件更新。如果shouldComponentUpdatetrue,會通過設置this.setState({})來刷新組件,並使得在組件更新結束之後,通知子組件更新。

      onStateChange() {
        this.selector.run(this.props)

        if (!this.selector.shouldComponentUpdate) {
          this.notifyNestedSubs()
        } else {
          this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate
          this.setState(dummyState)
        }
      }

在render操作時將selector生成的新props作為參數傳到WrappedComponent組件中,WrappedComponent組件就是connect()(ReactComponent)中傳入的ReactComponent

      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))
        }
      }

將selector.props中的屬性傳到WrappedComponent,這個props是經過計算的所有props的合集。

參考:

https://segmentfault.com/a/1190000010188279

https://segmentfault.com/a/1190000010113286

https://segmentfault.com/a/1190000010345345

React-redux原理探索