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.trySubscribe()
訂閱父組件傳來的store。subscription.notifyNestedSubs
,因爲子組件訂閱的是父組件的subscription,子組件觸發訂閱函數................java
connect能夠將store中的數據和方法進行處理後經過props傳入其包裹的組件中。react
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:緩存
mapStateToProps
不會執行。connect在每次dispatch後,都會調用store.getState()獲取最新的state,並使用lastState === currentState
判斷是否變化。並且在 redux combineReducers API中也作了優化,即當reducer中state沒有變化時,返回原來的state。mapStateToProps
執行和組件從新渲染的條件,因此能不傳的時候不要傳。mapDispatchToProps能夠將store.dispatch
或者dispatch一個action的方法((…args) => dispatch(actionCreator(…args))
)傳遞到其包裹的組件中。mapDispatchToProps有多種用法:ide
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) }
dispatch => bindActionCreators(mapDispatchToProps, dispatch)
)。默認狀況下,每次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()) }) } }
能夠在不用connect包裹組件的狀況下訂閱store或dispatch action。
下面是對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
優化。
返回dispatch的引用
const dispatch = useDispatch()
返回store的引用
const store = useStore()