PureComponent介紹
之前的一篇文章React效能優化方案之PureRenderMixin,是對react的shouldComponentUpdate的方法進行重寫去優化。但自從React15.3中新加了一個 PureComponent 類,易於在自己的元件使用,只需要將Component 換成 PureComponent 即可,所以原作者建議使用今天要介紹的PureComponent。
PureComponent原理
我們知道元件的state和props的改變就會觸發render,但是元件的state和props沒發生改變,render就不執行,只有PureComponent檢測到state或者props發生變化時,PureComponent才會呼叫render方法,因此,你不用手動寫額外的檢查,就可以達到提高效能的而目的。實際上react做了最外層的淺比較:
if (this._compositeType === CompositeTypes.PureClass) {
shouldUpdate = !shallowEqual(prevProps, nextProps) || ! shallowEqual(inst.state, nextState);
}
複製程式碼
shadowEqual只會淺檢查元件的props和state,所以巢狀物件和陣列是不會被比較的。深比較是要逐層遍歷列舉對應的鍵值進行比對,這個操作比較浪費時間。如果比較的深的情況,你也可以使用shouldComponentUpdate來手動單個比較是否需要重新渲染。最簡單的方式就是直接比較props或state:
shouldComponentUpdate(nextProps, nextState) {
return nextProps.xxx.xx === props.xxx.xx;
}
複製程式碼
另外,你可以使用immutable屬性,Immutable.js就是一個Immutable庫。這種情況下,屬性的比較是非常容易的,因為已存在的物件就不會發生改變,取而代之的是重新建立新的物件。
使用PureComponent
爬坑1:易變資料不能使用一個引用
PureComponent節約了我們的時間,避免了多餘的程式碼。那麼,掌握如何正確使用它是非常重要的,否則如果使用不當,它就無法發揮作用。例如,讓我們想想這樣一種情況,父元件有一個render方法和一個click處理方法:
handleClick() {
let {items} = this.state
items.push(`new-item`)
this.setState({ items })
}
render() {
return (
<div>
<button onClick={this.handleClick} />
<ItemList items={this.state.items} />
</div>
)
}
複製程式碼
由於ItemList是一個純元件,這時候點選它是不會被渲染的,但是我們的確向this.state.items加入了新的值,但是它仍然指向同一個物件的引用。但是,通過移除可變物件就很容易改變這種情況,使之能夠正確被渲染。
handleClick() {
this.setState(prevState => ({
words: prevState.items.concat([`new-item`])
}))
}
複製程式碼
爬坑2:不變資料使用一個引用
上面易變資料不能使用一個引用的案例中有一個點選刪除操作,如果我們刪除的程式碼這麼寫:
constructor(props){
super(props)
this.state={
items: [{a: 1}, {a: 2}, {a: 3}]
}
}
handleClick = () => {
const { items } = this.state;
items.splice(items.length - 1, 1);
this.setState({ items });
}
複製程式碼
items 的引用也是改變的,但如果 items 裡面是引用型別資料這時候state.items[0] === nextState.items[0]是false,子元件裡還是重新渲染了。這樣就需要我們保證不變的子元件資料的引用不能改變。這個時候可以使用前面說的immutable-js函式庫。
爬坑3:父給子通過props傳遞迴調函式
我們在子父通訊傳遞迴調函式時候:
// step1
<MyInput onChange={e => this.props.update(e.target.value)} />
// step2
update(e) {
this.props.update(e.target.value)
}
render() {
return <MyInput onChange={this.update.bind(this)} />
}
複製程式碼
由於每次 render 操作 MyInput 元件的 onChange 屬性都會返回一個新的函式,由於引用不一樣,所以父元件的 render 也會導致 MyInput 元件的 render
,即使沒有任何改動,所以需要儘量避免這樣的寫法,最好封裝:
update = (e) => {
this.props.update(e.target.value)
}
render() {
return <MyInput onChange={this.update} />
}
複製程式碼
爬坑4:新陣列,空陣列,也會導致元件重新渲染
<Entity values={this.props.values || []}/>
複製程式碼
為了避免這個問題,你可以使用defaultProps,它包含了一個屬性的初始化空狀態。在純元件(PureComponent)被建立時,因為函式的新物件被建立了,所以它會獲得新資料,並且重新渲染。解決這個問題最簡單的方法就是: 在元件的constructor方法中使用bind。
constructor(props) {
super(props)
this.update = this.update.bind(this)
}
update(e) {
this.props.update(e.target.value)
}
render() {
return <MyInput onChange={this.update} />
}
複製程式碼
同時,在JSX中,任何包含子元素(child elements)的元件,shallowEqual檢查總會返回false。
PureComponent使用方式
用法很簡單:
import React { PureComponent, Component } from `react`;
class Foo extends (PureComponent || Component) {
//...
}
複製程式碼
老版本相容寫法,在老版本的 React 裡也不會報錯的。
總結
1.純元件忽略重新渲染時,不僅會影響它本身,而且會影響它的說有子元素,所以,使用PureComponent的最佳情況就是展示元件,它既沒有子元件,也沒有依賴應用的全域性狀態
2.PureComponent 真正起作用的,只是在一些純展示元件上,複雜元件用了也沒關係,反正 shallowEqual 那一關就過不了,不過記得 props 和 state 不能使用同一個引用。