PureComponent
最先在 React v15.3 版本中發佈,主要是爲了優化 React 應用而產生。前端
class Counter extends React.PureComponent {
constructor(props) {
super(props);
this.state = {count: 1};
}
render() {
return (
<button color={this.props.color} onClick={() => this.setState(state => ({count: state.count + 1}))}> Count: {this.state.count} </button>
);
}
}
複製代碼
在這段代碼中, React.PureComponent
會淺比較 props.color
或 state.count
是否改變,來決定是否從新渲染組件。react
實現git
React.PureComponent
和 React.Component
相似,都是定義一個組件類。不一樣是 React.Component
沒有實現 shouldComponentUpdate()
,而 React.PureComponent
經過 props 和 state 的 淺比較 實現了。github
使用場景數組
當 React.Component
的 props 和 state 均爲基本類型,使用 React.PureComponent
會節省應用的性能緩存
可能出現的問題及解決方案性能優化
當props 或 state 爲 複雜的數據結構 (例如:嵌套對象和數組)時,由於 React.PureComponent
僅僅是 淺比較 ,可能會渲染出 錯誤的結果 。這時有 兩種解決方案 :數據結構
注意less
React.PureComponent
中的 shouldComponentUpdate()
將跳過全部子組件樹的 prop 更新(具體緣由參考 Hooks 與 React 生命週期:即:更新階段,由父至子去判斷是否須要從新渲染),因此使用 React.PureComponent 的組件,它的全部 子組件也必須都爲 React.PureComponent 。dom
對於 React 開發人員來講,知道什麼時候在代碼中使用 Component,**PureComponent ** 和 Stateless Functional Component 很是重要。
首先,讓咱們看一下無狀態組件。
輸入輸出數據徹底由 props
決定,並且不會產生任何反作用。
const Button = props =>
<button onClick={props.onClick}>
{props.text}
</button>
複製代碼
無狀態組件能夠經過減小繼承 Component
而來的生命週期函數而達到性能優化的效果。從本質上來講,無狀態組件就是一個單純的 render
函數,因此無狀態組件的缺點也是顯而易見的。由於它沒有 shouldComponentUpdate
生命週期函數,因此每次 state
更新,它都會從新繪製 render
函數。
React 16.8 以後,React 引入 Hooks 。它可讓你在不編寫 class 的狀況下使用 state 以及其餘的 React 特性。
PureComponent
?PureComponent
提升了性能,由於它減小了應用程序中的渲染操做次數,這對於複雜的 UI 來講是一個巨大的勝利,所以建議儘量使用。此外,還有一些狀況須要使用 Component
的生命週期方法,在這種狀況下,咱們不能使用無狀態組件。
無狀態組件易於實施且快速實施。它們適用於很是小的 UI 視圖,其中從新渲染成本可有可無。它們提供更清晰的代碼和更少的文件來處理。
React.memo
爲高階組件。它實現的效果與 React.PureComponent
類似,不一樣的是:
React.memo
用於函數組件React.PureComponent
適用於 class 組件React.PureComponent
只是淺比較 props
、state
,React.memo
也是淺比較,但它能夠自定義比較函數function MyComponent(props) {
/* 使用 props 渲染 */
}
// 比較函數
function areEqual(prevProps, nextProps) {
/* 若是把 nextProps 傳入 render 方法的返回結果與 將 prevProps 傳入 render 方法的返回結果一致則返回 true, 不然返回 false 返回 true,複用最近一次渲染 返回 false,從新渲染 */
}
export default React.memo(MyComponent, areEqual);
複製代碼
React.memo
經過記憶組件渲染結果的方式實現 ,提升組件的性能props
淺比較,若是相同,React 將跳過渲染組件的操做並直接複用最近一次渲染的結果。shouldComponentUpdate()
方法不一樣的是,若是 props 相等,areEqual
會返回 true
;若是 props 不相等,則返回 false
。這與 shouldComponentUpdate
方法的返回值相反。若是你在 render
方法裏建立函數,那麼使用 props
會抵消使用 React.PureComponent
帶來的優點。由於每次渲染運行時,都會分配一個新函數,若是你有子組件,即便數據沒有改變,它們也會從新渲染,由於淺比較 props
的時候總會獲得 false
。
例如:
// FriendsItem 在父組件引用樣式
<FriendsItem
key={friend.id}
name={friend.name}
id={friend.id}
onDeleteClick={() => this.deleteFriends(friend.id)}
/>
// 在父組件中綁定
// 父組件在 props 中傳遞了一個箭頭函數。箭頭函數在每次 render 時都會從新分配(和使用 bind 的方式相同)
複製代碼
其中,FriendsItem
爲 PureComponent
:
// 其中 FriendsItem 爲 PureComponent
class FriendsItem extends React.PureComponent {
render() {
const { name, onDeleteClick } = this.props
console.log(`FriendsItem:${name} 渲染`)
return (
<div> <span>{name}</span> <button onClick={onDeleteClick}>刪除</button> </div>
)
}
}
// 每次點擊刪除操做時,未刪除的 FriendsItem 都將被從新渲染
複製代碼
這種在 FriendsItem
直接調用 () => this.deleteFriends(friend.id)
,看起來操做更簡單,邏輯更清晰,但它有一個有一個最大的弊端,甚至打破了像 shouldComponentUpdate
和 PureComponent
這樣的性能優化。
這是由於:父組件在 render
聲明瞭一個函數onDeleteClick
,每次父組件渲染都會從新生成新的函數。所以,每次父組件從新渲染,都會給每一個子組件 FriendsItem
傳遞不一樣的 props
,致使每一個子組件都會從新渲染, 即便 FriendsItem
爲 PureComponent
。
避免在 render 方法裏建立函數並使用它。它會打破了像 shouldComponentUpdate 和 PureComponent 這樣的性能優化。
要解決這個問題,只須要將本來在父組件上的綁定放到子組件上便可。FriendsItem
將始終具備相同的 props
,而且永遠不會致使沒必要要的從新渲染。
// FriendsItem 在父組件引用樣式
<FriendsItem
key={friend.id}
id={friend.id}
name={friend.name}
onClick={this.deleteFriends}
/>
複製代碼
FriendsItem
:
class FriendsItem extends React.PureComponent {
onDeleteClick = () => {
this.props.onClick(this.props.id)
} // 在子組件中綁定
render() {
const { name } = this.props
console.log(`FriendsItem:${name} 渲染`)
return (
<div> <span>{name}</span> <button onClick={this.onDeleteClick}>刪除</button> </div>
)
}
}
// 每次點擊刪除操做時,FriendsItem 都不會被從新渲染
複製代碼
經過此更改,當單擊刪除操做時,其餘 FriendsItem
都不會被從新渲染了 👍
考慮一個文章列表,您的我的資料組件將從中顯示用戶最喜歡的 10 個做品。
render() {
const { posts } = this.props
// 在渲染函數中生成 topTen,並渲染
const topTen = [...posts].sort((a, b) =>
b.likes - a.likes).slice(0, 9)
return //...
}
// 這會致使組件每次從新渲染,都會生成新的 topTen,致使沒必要要的渲染
複製代碼
topTen
每次組件從新渲染時都會有一個全新的引用,即便 posts
沒有更改,派生 state
也是相同的。
這個時候,咱們應該將 topTen
的判斷邏輯提取到 render
函數以外,經過緩存派生 state
來解決此問題。
例如,在組件的狀態中設置派生 state
,並僅在 posts
已更新時更新。
componentWillMount() {
this.setTopTenPosts(this.props.posts)
}
componentWillReceiveProps(nextProps) {
if (this.props.posts !== nextProps.posts) {
this.setTopTenPosts(nextProps.posts)
}
}
// 每次 posts 更新時,更新派生 state,而不是在渲染函數中從新生成
setTopTenPosts(posts) {
this.setState({
topTen: [...posts].sort((a, b) => b.likes - a.likes).slice(0, 9)
})
}
複製代碼
在使用 PureComponent
時,請注意:
PureComponent
時,問題會更加複雜。// 新建了空方法ComponentDummy ,ComponentDummy 的原型 指向 Component 的原型;
function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;
/** * Convenience component with default shallow equality check for sCU. */
function PureComponent(props, context, updater) {
this.props = props;
this.context = context;
// If a component has string refs, we will assign a different object later.
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
} // 解析同 React.Component,詳細請看上一章
/** * 實現 React.PureComponent 對 React.Component 的原型繼承 */
/** * 用 ComponentDummy 的緣由是爲了避免直接實例化一個 Component 實例,能夠減小一些內存使用 * * 由於,咱們這裏只須要繼承 React.Component 的 原型,直接 PureComponent.prototype = new Component() 的話 * 會繼承包括 constructor 在內的其餘 Component 屬性方法,可是 PureComponent 已經有本身的 constructor 了, * 再繼承的話,形成沒必要要的內存消耗 * 因此會新建ComponentDummy,只繼承Component的原型,不包括constructor,以此來節省內存。 */
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
// 修復 pureComponentPrototype 構造函數指向
pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
// 雖然上面兩句已經讓PureComponent繼承了Component
// 但多加一個 Object.assign(),能有效的避免多一次原型鏈查找
Object.assign(pureComponentPrototype, Component.prototype);
// 惟一的區別,原型上添加了 isPureReactComponent 屬性去表示該 Component 是 PureComponent
// 在後續組件渲染的時候,react-dom 會去判斷 isPureReactComponent 這個屬性,來肯定是否淺比較 props、status 實現更新
/** 在 ReactFiberClassComponent.js 中,有對 isPureReactComponent 的判斷 if (ctor.prototype && ctor.prototype.isPureReactComponent) { return ( !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState) ); } */
pureComponentPrototype.isPureReactComponent = true;
複製代碼
這裏只是 PureComponent
的聲明建立,至於如何實現 shouldComponentUpdate()
,核心代碼在:
// ReactFiberClassComponent.js
function checkShouldComponentUpdate( workInProgress, ctor, oldProps, newProps, oldState, newState, nextContext, ) {
// ...
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
// 若是是純組件,比較新老 props、state
// 返回 true,從新渲染,
// 即 shallowEqual props 返回 false,或 shallowEqual state 返回 false
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
return true;
}
複製代碼
shallowEqual.js
/** * 經過遍歷對象上的鍵並返回 false 來執行相等性 * 在參數列表中,當任意鍵對應的值不嚴格相等時,返回 false。 * 當全部鍵的值嚴格相等時,返回 true。 */
function shallowEqual(objA: mixed, objB: mixed): boolean {
// 經過 Object.is 判斷 objA、objB 是否相等
if (is(objA, objB)) {
return true;
}
if (
typeof objA !== 'object' ||
objA === null ||
typeof objB !== 'object' ||
objB === null
) {
return false;
}
// 參數列表
const keysA = Object.keys(objA);
const keysB = Object.keys(objB);
// 參數列表長度不相同
if (keysA.length !== keysB.length) {
return false;
}
// 比較參數列表每個參數,但僅比較一層
for (let i = 0; i < keysA.length; i++) {
if (
!hasOwnProperty.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])
) {
return false;
}
}
return true;
}
複製代碼
Object.is()
判斷兩個值是否相同。
這種相等性判斷邏輯和傳統的 ==
運算不一樣,==
運算符會對它兩邊的操做數作隱式類型轉換(若是它們類型不一樣),而後才進行相等性比較,(因此纔會有相似 "" == false
等於 true
的現象),但 Object.is
不會作這種類型轉換。
這與 ===
運算符的斷定方式也不同。===
運算符(和==
運算符)將數字值 -0
和 +0
視爲相等,並認爲 Number.NaN
不等於 NaN
。
若是下列任何一項成立,則兩個值相同:
想看更過系列文章,點擊前往 github 博客主頁
走在最後,歡迎關注:前端瓶子君,每日更新