兩個前提:
shouldComponentUpdate
默認返回 true
,即只要組件的狀態(props
或者 state
)發生改變,組件就會執行 render
函數進行重渲染。除非重寫 shouldComponentUpdate
透過返回 false
阻止重渲染,或者讓組件直接繼承自 PureComponent
。PureComponent
的原理只不過代替你實現了 shouldComponentUpdate
:在函數內對當前和過去的 props
/state
進行淺對比(即僅比較對象的引用而非比較對象每個屬性值)。其實在 react-redux
中實現了這套邏輯,對數據進行淺對比:
import { connect } from 'react-redux'
function mapStateToProps(state) {
return {
todos: state.todos,
visibleTodos: getVisibleTodos(state),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
react-redux
會假設 App
是一個 PureComponent
,即對於唯一的 props
/state
有唯一的渲染結果。故 react-redux
首先會對根狀態(即上述代碼中 mapStateToProps
的第一個形參 state
)創建索引,進行淺對比,若對比結果一致則不對組件進行重渲染,否則繼續調用 mapStateToProps
函數;同時繼續對 mapStateToProps
返回的 props
對象中每個屬性值(state.todos
和 getVisibleTodos(state)
值)創建索引。和 shouldComponentUpdate
類似,只有當淺對比失敗,即索引發生變動時才會對封裝組件進行重渲染。
只有 state.todos
和 getVisibleTodos(state)
值不變,那麼 App
組件就永不會重渲染。但注意下面的陷阱模式:
function mapStateToProps(state) {
return {
data: {
todos: state.todos,
visibleTodos: getVisibleTodos(state),
}
}
}
即使兩者不再變化,但由於每次 mapStateToProps
返回結果 { data: { ... } }
中的 data
都是新創建的字面量對象,導致淺對比失敗,App
依然會重渲染。
其次是 combineReducers
。Redux Store 鼓勵我們把狀態對象劃分為不同碎片(slices)或領域(domains,業務),並分別編寫 reducer 函數以管理其狀態,最後使用 combineReducers
將這些領域及其 reducer 關聯起來,拼裝成一個整體的 state
。
combineReducers({ todos: myTodoReducer, counter: myCounterReducer })
combineReducers
會遍歷每一對領域(key 是領域名,value 是 reducer),對於每次遍歷:
hasChanged
設為 true
遍歷完後,combineReducers
就得到一個新狀態對象,透過 hasChanged
標識位就能判斷出整體狀態是否發生更改,若為 true
則新狀態會被返回給下游(下游指 react-redux
及其更下游的介面組件)。
綜上所述我們知道,當狀態需要發生更改時,務必讓相應的 reducer 函數始終返回新對象。修改原有對象的屬性值然後返回,並不會觸發組件的重渲染。
return Object.assign({}, state, { count: state.count++ })
// 而非僅修改原對象:
state.count++
return state
結上可知,無論是從 reducer 的定義上,還是從 Redux 的工作機制上,我們都走上了同一條 Object.assign
的模式,即不修改原狀態,只返回新狀態。可見 state 天生就是不可變異的(immutable)。
使用 ImmutableJS 能實現幾類不可變異的數據結構,譬如 Map
、List
。
import { Map } from 'immutable'
const person = Map() // 創建一個空對象
const personWithAge = person.set('age', 20) // 為 person 實例添加 age 屬性
// 調用 toJS() 打印出兩個實例
console.log(person.toJS()) // {},person 的屬性不變
console.log(personWithAge.toJS()) // { age: 20 }
在 Immutable 的數據結構中,當你想更改某個對象屬性時,你得到的永遠是一個新對象,而原對象不會變化。上述 reducer 中:
return state.set('count', state.get('count') + 1);
Immutable 的原理:
tea
屬性的值為 14
,首先找到訪問到 tea
節點的關鍵路徑:
完 :)