1. Component 和 PureComponent
react 包暴露出來的其中兩個基礎(chǔ)的 class(源碼中是以構(gòu)造函數(shù)的形式):Component
和 PureComponent
App extends React.Component {...}
用的爐火純青,但后者很少用妻顶。
React 生命周期函數(shù)中有一個 shouldComponentUpdate(nextProps, nextState)
譬淳, 此方法缺省時總是返回 true沉桌,即只要當前組件調(diào)用 this.setState()
疫铜,或者父組件產(chǎn)生更新压彭,那么當前組件總是會更新。
這可能有些性能浪費菌羽,所以你可以主動配置該生命周期函數(shù)掠械,判斷當 props
或 state
真的發(fā)生了變化,才返回 true注祖。
或者使用 PureComponent
猾蒂,其源碼中有句注釋:
Convenience component with default shallow equality check for sCU.
它實現(xiàn)了默認的淺比較方法,即:shouldComponentUpdate(nextProps, nextState)
方法是晨。
另外可以是使用 this.forceUpdate()
實現(xiàn)無論如何肚菠,強制更新。
[圖片上傳失敗...(image-d331d4-1575350157935)]
簡單看一下源碼:
/**
* Base class helpers for the updating state of a component.
*/
function Component(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;
// We initialize the default updater but the real one gets injected by the
// renderer.
this.updater = updater || ReactNoopUpdateQueue;
}
/*
* @param {object|function} partialState Next partial state or function to
* produce next partial state to be merged with current state.
* @param {?function} callback Called after state is updated.
* @final
* @protected
*/
Component.prototype.setState = function(partialState, callback) {
invariant(
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null,
'setState(...): takes an object of state variables to update or a ' +
'function which returns an object of state variables.',
);
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
/*
* @param {?function} callback Called after update is complete.
* @final
* @protected
*/
Component.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};
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;
}
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;
源碼中可以看出兩個“類” 本身定義時沒多大區(qū)別罩缴,PureComponent
只是繼承了 Component
蚊逢,并多設(shè)置了一個屬性 pureComponentPrototype.isPureReactComponent = true
,PureComponent
這個標記就很有用了靴庆,會在別處被用來指導是否進行淺比較,從而決定是否更新怒医,props
或者 state
發(fā)生變化后炉抒,就應(yīng)該更新:
if (type.prototype && type.prototype.isPureReactComponent) {
shouldUpdate =
!shallowEqual(oldProps, props) || !shallowEqual(oldState, state);
}
3. React.memo
3.1 基本用法
React.memo()
是 React v16.6 中引入的新功能,和 React.PureComponent
類似稚叹,可以用于函數(shù)組件的渲染焰薄,第一個參數(shù)是函數(shù)組件拿诸,第二個可選參數(shù)是自定義的判斷函數(shù):
React.memo(Component, [areEqual(prevProps, nextProps)]);
export function Movie({ title, releaseDate }) {
return (
<div>
<div>Movie title: {title}</div>
<div>Release date: {releaseDate}</div>
</div>
);
}
export const MemoizedMovie = React.memo(Movie);
// 第二個可選參數(shù)
function moviePropsAreEqual(prevMovie, nextMovie) {
return prevMovie.title === nextMovie.title
&& prevMovie.releaseDate === nextMovie.releaseDate;
}
const MemoizedMovie2 = React.memo(Movie, moviePropsAreEqual);
props 和 state 變化后,react 默認總是執(zhí)行 render塞茅,當然亩码,即使得到了新的虛擬 DOM,也會和舊的 DOM 做 diff 對比野瘦,只有產(chǎn)生了實質(zhì)變化描沟,才更新真實 DOM。而使用本文介紹的 React.PureComponent
和 React.memo()
鞭光,則是為了在一開始就試圖跳過組件的重新渲染吏廉,自然也更不會再對比 虛擬 DOM 了。
3.2 使用場景
React.memo
使用場景是組件頻繁 re-render惰许,且每次接收的 props
經(jīng)常是相同的值席覆。
3.3 注意事項
避免被 callback 破壞初衷
使用 React.memo
另外要注意如果 props 中有父組件傳來的 callback,則應(yīng)保證傳入的 callback 每次是相同的實例汹买,反面教材:
function MyApp({ store, cookies }) {
return (
<div className="main">
<header>
<MemoizedLogout
username={store.username}
onLogout={() => cookies.clear('session')}
/>
</header>
{store.content}
</div>
);
}
function Logout({ username, onLogout }) {
return (
<div onClick={onLogout}>
Logout {username}
</div>
);
}
const MemoizedLogout = React.memo(Logout);
可以在父組件中使用 useCallback()
來保存 callback佩伤,這樣即使多次 render 也是最初的 callback 實例:
function MyApp({ store, cookies }) {
const onLogout = useCallback(
() => cookies.clear('session'),
[cookies]
);
return (
<div className="main">
<header>
<MemoizedLogout
username={store.username}
onLogout={onLogout}
/>
</header>
{store.content}
</div>
);
}
useState()
使用 useState()
時若 state 改變,react 總是會保證 re-render 組件晦毙,即使該組件使用了 React.memo()
生巡。
3.4 尾記
對 react 的這些優(yōu)化究竟真的變快了,還是沒什么區(qū)別结序,亦或者強行優(yōu)化反而“反模式”障斋,這就需要一定的衡量手法和指標:profiling
參考: