超级面板
文章目录
最新文章
最近更新
文章分类
标签列表
文章归档

React.momo

概述

如果你经常使用 PureComponentshouldComponentUpdate 进行性能优化的时候,那么一定对纯函数组件(SFC 组件)缺失这样的功能而感觉不爽,甚至有时候不得不对有些复杂的组件进行一层包装,包装成类组件。

现在,React 官方终于提供了对这个功能的支持。在 React v16.6.0 新引入了一个高阶组件 React.momo,它的作用类似于 React.PureComponent 但是是作用在纯函数组件(SFC 组件)上的。

const MyComponent = React.memo(function MyComponent(props) {
/* 只有在 props 改变的时候才会重新渲染 */
});

也就是说,在 props 没有改变的情况下,Reactc将跳过重新渲染组件,而是重用最近一次的渲染结果。

自定义比较函数

当然,与 React.PureComponent 一样,在默认情况下,它只会浅比较 props 对象中的复杂对象。

如果要更加准确的控制比较的结果,还可以提供自定义的比较函数作为 React.momo 的第二个参数。

function MyComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
}
export default React.memo(MyComponent, areEqual);

注意,这个自定义的比较函数和 shouldComponentUpdate() 对返回结果的处理正好相反,也就是说两次的 props 相同的时候返回 true,不同则返回 false。返回 true 会阻止更新,使用上次渲染的结果,而返回 false 则重新渲染。

需要注意的是,根据官方文档,这个函数仅仅被定义为进行性能优化,不要出于业务逻辑的需要使用它来阻止更新,否则可能造成一些问题。

This method only exists as a performance optimization. Do not rely on it to “prevent” a render, as this can lead to bugs.

自己实现

知道了怎么用,以及它的原理,也可以来写一下实现:

使用类包装

这是最简单的一种方式,通过将函数式组件包装成为类组件:

const momo = (sfcRender, compareFunc) => {
if (compareFunc) {
return class extends React.Component {
shouldComponentUpdate(prevProps, nextProps) {
// 这里行为要相反
return !compareFunc(prevProps, nextProps);
}
render() {
return sfcRender(this.props);
}
}
}
return class extends React.PureComponent {
render() {
return sfcRender(this.props);
}
}
}

使用函数缓存

另一种实现方式是将相关数据缓存到函数上。

const momo = (sfcRender, compareFunc) => {
sfcRender.__prevResult = null;
sfcRender.__prevProps = null;
sfcRender.__isFirstRender = true;
return function(nextProps) {
if (sfcRender.__isFirstRender) {
sfcRender.__isFirstRender = false;
sfcRender.__prevResult = sfcRender(nextProps);
sfcRender.__prevProps = nextProps;
return sfcRender.__prevResult;
}

const isCustomeSame = compareFunc && compareFunc(sfcRender.__prevProps, nextProps);
const isShadowSame = !compareFunc && nextProps === sfcRender.__prevProps;
sfcRender.__prevProps = nextProps;
if (!isCustomeSame && !isShadowSame) {
sfcRender.__prevResult = sfcRender(nextProps);
}
return sfcRender.__prevResult;
};
}

注意,这两种实现仅供参考。这两种方式如果使用在单个的组件上,则其行为一致。但是,当应用到多个组件上时,具有较大的行为差异。第一种方式由于每次在使用的时候都生成了一个新的示例,所以仅在每次父组件更新时,才会触发比较判断是否更新。第二种方式由于是函数的实现方式,所以当首次渲染渲染多个包装过的函数式组件时,即进行判断。

TODO:React 源码实现方式