Component/PureComponent/React.me
1. Component 和 PureComponent
react 包暴露出来的其中两个基础的 class(源码中是以构造函数的形式):Component
和 PureComponent
App extends React.Component {...}
用的炉火纯青,但后者很少用。
React 生命周期函数中有一个 shouldComponentUpdate(nextProps, nextState)
, 此方法缺省时总是返回 true,即只要当前组件调用 this.setState()
,或者父组件产生更新,那么当前组件总是会更新。
这可能有些性能浪费,所以你可以主动配置该生命周期函数,判断当 props
或 state
真的发生了变化,才返回 true。
或者使用 PureComponent
,其源码中有句注释:
Convenience component with default shallow equality check for sCU.
它实现了默认的浅比较方法,即:shouldComponentUpdate(nextProps, nextState)
方法。
另外可以是使用 this.forceUpdate()
实现无论如何,强制更新。
[图片上传失败...(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;
源码中可以看出两个“类” 本身定义时没多大区别,PureComponent
只是继承了 Component
,并多设置了一个属性 pureComponentPrototype.isPureReactComponent = true
,PureComponent
这个标记就很有用了,会在别处被用来指导是否进行浅比较,从而决定是否更新,props
或者 state
发生变化后,就应该更新:
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
类似,可以用于函数组件的渲染,第一个参数是函数组件,第二个可选参数是自定义的判断函数:
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);
// 第二个可选参数
function moviePropsAreEqual(prevMovie, nextMovie) {
return prevMovie.title === nextMovie.title
&& prevMovie.releaseDate === nextMovie.releaseDate;
}
const MemoizedMovie2 = React.memo(Movie, moviePropsAreEqual);
props 和 state 变化后,react 默认总是执行 render,当然,即使得到了新的虚拟 DOM,也会和旧的 DOM 做 diff 对比,只有产生了实质变化,才更新真实 DOM。而使用本文介绍的 React.PureComponent
和 React.memo()
,则是为了在一开始就试图跳过组件的重新渲染,自然也更不会再对比 虚拟 DOM 了。
3.2 使用场景
React.memo
使用场景是组件频繁 re-render,且每次接收的 props
经常是相同的值。
3.3 注意事项
避免被 callback 破坏初衷
使用 React.memo
另外要注意如果 props 中有父组件传来的 callback,则应保证传入的 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 的这些优化究竟真的变快了,还是没什么区别,亦或者强行优化反而“反模式”,这就需要一定的衡量手法和指标:profiling
参考: