优化 React 项目性能的几个技巧
本文将介绍几种优化 React 应用性能的方法,并通过示例演示如何在进行优化时选择合适的解决方案。
在开始之前,我们先来看一个例子。
这里有24个复选框,供用户选择他们想要的时间。
这个例子的问题在于,每次用户点击复选框时,所有复选框都会重新渲染。
那么,我们该如何解决这个问题呢?
React DevTools 分析器
在开始优化应用程序之前,我们需要知道如何识别应用程序的性能问题?
react-dom 16.5+ 和 react-native 0.57+ 通过React DevTools Profiler提供了增强的性能分析功能。
使用 React DevTools Profiler 非常简单,只需点击左上角的录制按钮,与应用程序交互,然后再次点击该按钮停止录制即可。这样我们就能获得结果,从而识别问题所在。
当我们通过 React DevTools Profiler 检查火焰图时,可以看到不必要的重新渲染。
既然我们知道了问题所在,那就让我们尝试几种不同的解决方法吧。
纯组分
首先,我们可以尝试最简单的解决方案——PureComponent ,我们只需要将类继承从component更改为PureComponent,然后React会为我们完成剩下的工作。
// before
export default class CheckBox extends React.Component {
...
}
// after
export default class CheckBox extends React.PureComponent {
...
}
但是,即使我们进行了更改PureComponent,也发现并没有阻止不必要的重新渲染。原因是每次都会创建一个新的 handleToggle 函数。因此,即使我们应用了PureComponent该函数,当 App 组件重新渲染时,所有 CheckBox 组件仍然会被重新渲染。
应组件更新
因为PureComponent之前的方法行不通。所以现在我们需要自己进行检查。我们可以使用ShouldComponentUpdate来阻止不必要的渲染。
shouldComponentUpdate(nextProps) {
const {value, isChecked} = nextProps;
return this.props.value !== value || this.props.isChecked !== isChecked
}
现在,当我们再次检查 React DevTools Profiler 时,我们会发现只有点击复选框会重新渲染。
React.memo
如果我们想使用函数组件而不是类组件,还有另一种选择——React.memo。React.memo它会执行与 `<class>` 相同的检查PureComponent。但它允许我们传递第二个参数来进行自定义检查,类似于 `<class>` ShouldComponentUpdate。但我们需要注意的是,它的返回值应该与 `<class>` 相反ShouldComponentUpdate。
export default React.memo(CheckBox, (prevProps, nextProps) => {
return prevProps.value === nextProps.value && prevProps.isChecked === nextProps.isChecked
});
使用备忘录
函数组件的另一种解决方案是使用钩子 - useMemo。
export default function CheckBox ({value, isChecked, handleToggle}){
return React.useMemo(() => {
return (
<div>
<label>
<input type="checkbox" value={value} checked={isChecked} onChange={handleToggle} />
{value}
</label>
</div>
)
}, [value, isChecked]);
}
虽然这可以帮助我们避免不必要的重新渲染,但我们会看到来自eslint-plugin-react-hooks的错误。
它eslint会提醒我们将其添加handleToggle到依赖项数组中。但我们不能这样做,因为为了防止不必要的重新渲染,我们必须忽略它。我们可以轻松地使用 `resolve`eslint-disable来避免此错误。但实际上,此错误消息确实指出了一个重要的问题。
尽管上述大多数解决方案(除了[此处应填写PureComponent具体方法名称])可以帮助我们优化性能,但这些自定义逻辑也会增加代码的维护难度,并可能引入一些潜在的错误。
例如,当其他团队成员为复选框组件添加一个新的属性时,如果他/她忘记在[此处应填写具体方法名称]或[此处应填写具体方法名称]isDarkMode中调整自定义逻辑,那么深色模式将无法正常工作,因为复选框组件在属性更改时不会重新渲染。ShouldComponentUpdateReact.memoisDarkMode
那么,我们该如何解决这个问题呢?
使用回调
解决这个性能问题的更好方法是避免handleToggle每次都创建新函数。
我们可以将 App 组件改为类组件,或者使用另一个钩子函数useCallback来完成这项工作。
const handleToggle = useCallback(targetTime => {
setTimeCheckboxes(timeCheckBoxes => {
return timeCheckBoxes.map(({ time, isChecked }) => ({
time,
isChecked: targetTime === time ? !isChecked : isChecked
}));
});
}, []);
因为我们toggle现在无需每次都创建新函数,只需应用于PureComponent复选框即可。这样,我们就能在不向代码库添加任何自定义逻辑的情况下,避免不必要的重新渲染。
测量
此外,我们不仅需要了解如何优化我们的应用程序,还需要知道如何衡量我们应用程序的性能。
React Profiler
React 提供了一个组件来帮助我们实现这一点——Profiler 。只需
将我们的 App 组件包裹在 Profiler 中Profiler,即可获取所需的信息。
<Profiler id="app" onRender={onRenderCallback}>
<div className="App">
...
</div>
</Profiler>
onRenderprop 会将这些信息传递给我们的回调函数。这样我们就可以打印出所需的信息了。
function onRenderCallback(
id, // the "id" prop of the Profiler tree that has just committed
phase, // either "mount" (if the tree just mounted) or "update" (if it re-rendered)
actualDuration, // time spent rendering the committed update
baseDuration, // estimated time to render the entire subtree without memoization
startTime, // when React began rendering this update
commitTime, // when React committed this update
interactions // the Set of interactions belonging to this update
) {
// Aggregate or log render timings...
}
现在我们可以知道优化前后的区别了。
Chrome 开发者工具:性能
另一种方法是使用 Chrome 开发者工具。我们可以选择Performance标签页并开始录制,就像我们在 React DevTools Profiler 中所做的那样。
(这里我降低了 CPU 使用率,以便更容易地识别性能问题;如果需要,我们也可以模拟较慢的网络。)
然后我们可以看到这样的结果。
- 之前:152.72毫秒,132.22毫秒,204.83毫秒

- (应用
useCallback和PureComponent)之后:15.64毫秒,18.10毫秒,12.32毫秒
结论
React 提供了许多 API 和工具来帮助我们优化应用程序。在优化性能的同时,我们需要谨慎选择解决方案,以确保在提升应用程序性能后,代码仍然易于维护。
--

