本文深入淺出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