本文深入淺出mapStateToProps,解答:
- 為什么修改state,組件未渲染/更新?
- 如何從新舊state判斷更新的值、未更新的值,從而決定是否re-render?
Redux中,
state作為單一的數據源,眾所周知,每次更新state都要通過return { ...state, others }來返回一個新的state,但是它是怎么來判斷一些組件到底要不要re-render(刷新、重渲染)呢?尤其是當state層次很深的時候,會有效率問題、該刷新時不刷新的問題嗎?
其實關鍵在於這個connect()函數的第一個參數:mapStateToProps。下面舉個例子:
/* 假設state的結構如下: state = { user: {username: 'root', name: '根'}; globals: {showMenu: true, showModal: false}; }; */ function mapStateToProps(state) { return { user: state.user, // 比如常見的獲取用戶信息 showMenu: state.globals.showMenu, // 比如state層次有多層,可以直接獲取到那個深層的數據 }; } export default connect(mapStateToProps)(MyComponent);
眾所周知,mapStateToProps必須是個純函數,只要有相同的輸入,一定會有相同的輸出,不會有任何副作用,沒有異步操作。輸入是state,輸出是一個對象,這個對象會變為被connect組件的props。
其實,這個函數通常是選取了state的一個子集,通過props映射到了組件上。如果這個子集更新了,那么組件就會re-render。具體原理、過程如下:
原理
當state更新時(即nextState !== state,注意這里用===來比較,所以每次更新state需要用文章開頭的方式來更新),
react-redux會調用所有的mapStateToProps函數(所以每個mapStateToProps函數應該很快能運算結束,不然會成為應用性能瓶頸),
針對每次調用mapStateToProps,檢查函數的結果(是個對象)的每個key的value跟上一次對應的value是否一致(這里也是用===來比較!)如果所有value都一致,不會渲染這個組件;如果有一個value變了,就重新渲染。
PS. 所以react-redux中對mapStateToProps的結果的比較是淺比較,即會遍歷所有key來判斷是否相等,對每個key的value的判斷方法是===。所以,要搞清楚“===”、“淺比較”、“深比較”的差別。
舉例說明
用上面舉例提到的mapStateToProps函數。(所以每次會比較返回值key是user的value和key是showMenu的value是否相等)
function mapStateToProps(state) { return { user: state.user, showMenu: state.globals.showMenu, }; }
假設有初始state如下:
state = { user: {username: 'root', name: '根'}; globals: {showMenu: true, showModal: false}; };
案例1:一個正確示范
比如有一個某個操作dispatch了一個action,返回新的state的代碼如下:
return { ...state, user: {username: 'foo', name: 'bar'} };
通過對比,發現showMenu還是原來的showMenu,但是user變成了新的對象,所以,重新渲染!
案例2:一個正確示范,可看出redux對性能的優化,reducer要用淺拷貝!
比如有一個某個操作dispatch了一個action,返回新的state的代碼如下:
const globals = {...state.globals, showModal: true};
return { ...state, globals };
通過對比,發現showMenu還是原來的showMenu(因為都是true,發現相等),而且user也是原來的user,所以,不會重新渲染!
注意,這里的state.globals已經不是原來的state.globals了,但是函數返回的對象中:key是showMenu的value沒變,所以不會重新渲染。
思考1:如果組件中用到的是某個state的某個部分的值,mapStateToProps函數一定要盡可能細化到它,這有助於優化!
思考2:寫reducer更新state時,淺拷貝就夠了,千萬不要深拷貝!
// 如果有這樣的狀態 state = { articles: {size: 'big', list: ['1', '2', '3', '4']}, }; // 想更新state.articles.size這樣寫,好! const articles = { ...state.articles, size: 'small'}; return { ...state, articles }; // 這樣寫會導致與list有關的組件而與size無關的組件也被重新渲染,不好!而且深拷貝性能損耗更多! const articles = {size: 'small', list: [...state.articles.list]}; return { ...state, articles };
案例3,錯誤示范
比如有一個某個操作dispatch了一個action,返回新的state的代碼如下:
const user = state.user; user.username = 'foo'; user.name = 'bar'; return { ...state, user };
通過對比,發現showMenu還是原來的showMenu,而user也是原來的user(強調:state.user依然是原來的,並沒有創建或復制一個新的對象,const user = state.user;只是復制了引用,不算淺拷貝;不過還是要注意返回的state跟之前的state已經不是同一個了),所以,不會重新渲染!
怎么修改?答案如下:
const user = { ...state.user, username: 'foo', name: 'bar', }; return { ...state, user };
案例4,更錯誤的示范
比如有一個某個操作dispatch了一個action,返回新的state的代碼如下:
state.user = {username: '', name: ''};
return state;
這個時候mapStateToProps函數都懶得理你,它不會執行的。因為state根本沒變。
原文鏈接:https://blog.csdn.net/kd_2015/article/details/105277509
