React-redux總結

React-redux總結

1.Provider

function Provider({store, context, children}){
    //contextValue初始化
    const contextValue = useMemo(()=>{
        const subscription = new Subscription(store);
        subscription.onStateChange = subscription.notifyNestedSubs;
      //自定義訂閱模型供子組件訂閱
        return {
            store,
            subscription
        }
    }, [store])
    const previosState = useMemo(()=> store.getState(),[store]);
    useEffect(()=>{
        const { subscription } = contextValue;
      //根節點訂閱原始store,子節點訂閱父組件中的subscription
        subscription.trySubscribe();
        if(previosState !== store.getState()){
          //訂閱模型造成網狀結構,保證訂閱函數的執行順序爲父節點到子節點的網狀結構,防止子節點的訂閱者先觸發致使過時props問題。
            subscription.notifyNestedSubs();
        }
        return ()=>{
            subscription.tryUnsubscribe();
            subscription.onStateChange = null;
        }
    },[contextValue, previosState])
    const Context =context || ReactReduxContext;
    return <Context.Provider value={contextValue}>{children}</Context.Provider>
}

上面的邏輯能夠簡述以下:javascript

  • 新建一個訂閱模型subscription供子組件訂閱
  • 在useEffect中subscription調用subscription.trySubscribe()訂閱父組件傳來的store。
  • 當store因爲dispatch觸發訂閱函數時,執行subscription.notifyNestedSubs,因爲子組件訂閱的是父組件的subscription,子組件觸發訂閱函數

    ................java

  • 將改造後的store經過contextAPI傳入子組件。

2. Connect

connect能夠將store中的數據和方法進行處理後經過props傳入其包裹的組件中。react

2.1 mapStateToProps: (state, ownProps) => stateProps

mapStateToProps方法主要用來對store中數據進行reshape。由於每次store變化都會觸發全部connect中的mapStateToProps函數執行,因此該函數應該運行的足夠快以避免影響性能。必要的時候可使用selector庫來避免沒必要要的計算(相同輸入的狀況下不進行再次計算,而是使用上一次的緩存值)。ios

react-redux進行了不少優化以免沒必要要的重複渲染,redux

(state) => stateProps (state, ownProps) => stateProps
mapStateToProps 執行條件: store state 變化 store state 變化或者ownProps變化
組件從新渲染條件: stateProps變化 stateProps變化或者ownProps變化

從上表能夠總結一些tips:緩存

  • 判斷stateProps變化是採用shallow equality checks比較的, 每次執行(state, ownProps) => stateProps即便輸入值同樣,若是 stateProps中的每一個field返回了新對象,也會觸發從新渲染。可使用selector緩存上一次的值來避免stateProps變化。
  • 當store state沒有變化時,mapStateToProps不會執行。connect在每次dispatch後,都會調用store.getState()獲取最新的state,並使用lastState === currentState判斷是否變化。並且在 redux combineReducers API中也作了優化,即當reducer中state沒有變化時,返回原來的state。
  • ownProps也是mapStateToProps執行和組件從新渲染的條件,因此能不傳的時候不要傳。

2.2 mapDispatchToProps

mapDispatchToProps能夠將store.dispatch或者dispatch一個action的方法((…args) => dispatch(actionCreator(…args)))傳遞到其包裹的組件中。mapDispatchToProps有多種用法:ide

  • 不傳遞mapDispatchToProps時,將會將dispatch方法傳遞到其包裹的組件中。
  • mapDispatchToProps定義爲函數時,(dispatch,ownProps)=>dispatchProps函數

    const increment = () => ({ type: 'INCREMENT' })
    const decrement = () => ({ type: 'DECREMENT' })
    const reset = () => ({ type: 'RESET' })
    
    const mapDispatchToProps = dispatch => {
      return {
        // dispatching actions returned by action creators
        increment: () => dispatch(increment()),
        decrement: () => dispatch(decrement()),
        reset: () => dispatch(reset())
      }
    }

    redux中提供了bindActionCreators接口能夠自動的進行actionCreators到相應dispatch方法的轉換:性能

    import { bindActionCreators } from 'redux'
    
    const increment = () => ({ type: 'INCREMENT' })
    const decrement = () => ({ type: 'DECREMENT' })
    const reset = () => ({ type: 'RESET' })
    
    function mapDispatchToProps(dispatch) {
      return bindActionCreators({ increment, decrement, reset }, dispatch)
    }
  • mapDispatchToProps定義爲action creators鍵值對時,connect內部會自動調用bindActionCreators將其轉化爲dispatching action函數形式(dispatch => bindActionCreators(mapDispatchToProps, dispatch))。

3. batch

默認狀況下,每次dispatch都會執行connect函數,並執行接下來可能的重複渲染過程,使用batchAPI,能夠將屢次dispatch合併,相似setState的合併過程。優化

import { batch } from 'react-redux'

function myThunk() {
  return (dispatch, getState) => {
    // should only result in one combined re-render, not two
    batch(() => {
      dispatch(increment())
      dispatch(increment())
    })
  }
}

4. hooks

能夠在不用connect包裹組件的狀況下訂閱store或dispatch action。

4.1 useSelector()

下面是對useSelector的簡單實現:

const useSelector = selector => {
  const store = React.useContext(Context);
  const [, forceUpdate] = React.useReducer(c => c + 1, 0);
  const currentState = React.useRef();
  // 在re-render階段更新state使得獲取的props不是過時的
  currentState.current = selector(store.getState());

  React.useEffect(() => {
    return store.subscribe(() => {
      try {
        const nextState = selector(store.getState());

        if (nextState === currentState.current) {
          // Bail out updates early
          return;
        }
      } catch (err) {
        // Ignore errors
        //忽略因爲過時props帶來的計算錯誤
      }
            //state變化須要從新渲染
      // Either way we want to force a re-render(與其餘訂閱者中的forceUpdate合併執行)
      forceUpdate();
    });
  }, [store, forceUpdate, selector, currentState]);

  return currentState.current;
};

useSelector中新舊state的對比使用===,而不是connect中的淺比較,因此selector返回一個新的對象會致使每次從新渲染。對於這個問題,可使用多個selector返回基本類型的值來解決;或者使用reselect庫緩存計算結果;最後還能夠傳遞useSelector的第二個參數,自定義新舊state的比較函數。

connect中會對新舊state和props值進行比較來決定是否執行mapStateToProps,可是useSelector中沒有這種機制,因此不會阻止因爲父組件的re-render致使的re-render(即便props不變化),這種狀況下能夠採用React.memo優化。

4.2 useDispatch()

返回dispatch的引用

const dispatch = useDispatch()

4.3 useStore()

返回store的引用

const store = useStore()
相關文章
相關標籤/搜索