何時使用Component還是PureComponent?
我開始轉向使用PureCompoent
是因為它是一個更具效能的Component
的版本。雖然事實證明這是正確的,但是這種效能的提高還伴隨著一些附加的條件。讓我們深挖一下PureComponent
,並理解為什麼我們應該使用它。
Component和PureComponent有一個不同點
除了為你提供了一個具有淺比較的shouldComponentUpdate
方法,PureComponent
和Component
基本上完全相同。當props
或者state
改變時,PureComponent
將對props
和state
進行淺比較。另一方面,Component不會比較當前和下個狀態的props
和state
。因此,每當shouldComponentUpdate
被呼叫時,元件預設的會重新渲染。
淺比較101
當把之前和下一個的props
和state
作比較,淺比較將檢查原始值是否有相同的值(例如:1 == 1
或者ture==true
),陣列和物件引用是否相同。
從不改變
您可能已經聽說過,不要在props
和state
中改變物件和陣列,如果你在你的父元件中改變物件,你的“pure”子元件不將更新。雖然值已經被改變,但是子元件比較的是之前props
的引用是否相同,所以不會檢測到不同。
因此,你可以通過使用es6的assign方法或者陣列的擴充套件運算子或者使用第三方庫,強制返回一個新的物件。
存在效能問題?
比較原始值值和物件引用是低耗時操作。如果你有一列子物件並且其中一個子物件更新,對它們的props
和state
進行檢查要比重新渲染每一個子節點要快的多。
其它解決辦法
不要在render的函式中繫結值
假設你有一個專案列表,每個專案都傳遞一個唯一的引數到父方法。為了繫結引數,你可能會這麼做:
<CommentItem likeComment={() => this.likeComment(user.id)} />
這個問題會導致每次父元件render方法被呼叫時,一個新的函式被建立,已將其傳入likeComment
。這會有一個改變每個子元件props
的副作用,它將會造成他們全部重新渲染,即使資料本身沒有發生變化。
為了解決這個問題,只需要將父元件的原型方法的引用傳遞給子元件。子元件的likeComment
屬性將總是有相同的引用,這樣就不會造成不必要的重新渲染。
<CommentItem likeComment={this.likeComment} userID={user.id} />
然後再子元件中建立一個引用了傳入屬性的類方法:
class CommentItem extends PureComponent {
...
handleLike() {
this.props.likeComment(this.props.userID)
}
...
}
不要在render方法裡派生資料
考慮一下你的配置元件將從一系列文章中展示使用者最喜歡的十篇文章。
render() {
const { posts } = this.props
const topTen = posts.sort((a, b) => b.likes - a.likes).slice(0, 9)
return //...
}
每次元件重新渲染時topTen
都將有一個新的引用,即使posts
沒有改變並且派生資料也是相同的。這將造成列表不必要的重新渲染。
你可以通過快取你的派生資料來解決這個問題。例如,設定派生資料在你的元件state
中,僅當posts更新時它才更新。
componentWillMount() {
this.setTopTenPosts(this.props.posts)
}
componentWillReceiveProps(nextProps) {
if (this.props.posts !== nextProps.posts) {
this.setTopTenPosts(nextProps)
}
}
setTopTenPosts(posts) {
this.setState({
topTen: posts.sort((a, b) => b.likes - a.likes).slice(0, 9)
})
}
如果你正在使用Redux,可以考慮使用reselect來建立”selectors”來組合和快取派生資料。
結束語
只要你遵循下列兩個簡單的規則就可以安全的使用PureComponent
來代替Component
:
- 雖然通常情況下易變性就是不好的,但是當使用`PureComponent`時問題會變得複雜。
- 如果你在`render`方法中建立一個新的函式,物件或者是陣列那麼你的做法(可能)是錯誤的。