React.memo 的用途
优化 React
优化函数组件的性能
React.memo
是 React 库中的一个高阶组件,旨在优化函数组件的性能。当一个函数组件在相同的 props 下反复渲染时,React.memo
允许 React 跳过渲染步骤并直接复用最后一次渲染的结果。
工作原理
当一个包裹在 React.memo
中的组件接收到新的 props 时,React 将执行一个浅层比较来判断是否需要重新渲染。如果 props 没有变化,组件则不会重新渲染,从而减少不必要的计算和 DOM 操作。
语法
1const MemoizedComponent = React.memo(MyComponent);
参数
MyComponent
:待优化的函数组件。areEqual
(可选):一个用于自定义比较函数的参数,接收两个参数,即新旧 props,返回一个布尔值。
应用场景
- 性能优化:当一个组件在相同的 props 下反复渲染时。
- 避免副作用:当重新渲染可能触发不必要的副作用或计算时。
限制和注意事项
- 只用于函数组件:
React.memo
是为函数组件设计的,不应用于 class 组件。 - 浅层比较:默认情况下,比较是浅层的,可能不适用于深层嵌套的对象。
- 副作用与复杂性:过度使用
React.memo
可能会引入额外的复杂性并影响代码可读性。
示例
1const MyComponent = ({ name }) => {
2 console.log('Rendered:', name);
3 return <div>{name}</div>;
4};
5
6const areEqual = (prevProps, nextProps) => {
7 return prevProps.name === nextProps.name;
8};
9
10const MemoizedMyComponent = React.memo(MyComponent, areEqual);
总体而言,React.memo
是一个用于优化 React 函数组件性能的有用工具,但需要谨慎使用,以避免不必要的复杂性和潜在问题。
性能提升的误区
根据经验,对 props
对象进行浅层比较总是比调用 React.createElement()
和比较整个组件树要便宜。
但是,如果你的组件有深度比较的 props,那么 React.memo
的浅比较是不够的。此外,如果你的组件接收并显示 children
属性值,则此高阶组件不起作用。
如下代码:
1import type { FC, PropsWithChildren } from 'react';
2import { memo, useEffect, useState } from 'react';
3
4const _Child: FC<PropsWithChildren> = ({ children }) => {
5 console.log('Child');
6 return <div>{children}</div>;
7};
8
9const Child = memo(_Child);
10
11export const Test = () => {
12 const [num, setNum] = useState(0);
13 console.log(num);
14
15 useEffect(() => {
16 console.log('useEffect');
17 setNum(1);
18 }, []);
19
20 return (
21 <div>
22 <Child>
23 <div>Child</div>
24 </Child>
25 </div>
26 );
27};
严格模式下输出如下:
Test.tsx:13 0
Test.tsx:13 0
Test.tsx:5 Child
Test.tsx:5 Child
Test.tsx:16 useEffect
Test.tsx:16 useEffect
Test.tsx:13 1
Test.tsx:13 1
Test.tsx:5 Child
Test.tsx:5 Child
当接受一个非 primitive 的 children(当然不仅是 children, children 也只是一个普通的 prop。) 时,memo 不能起到任何作用反而增加一次浅比较和无效缓存。使用 useMemo 包裹 <div>Child</div>
可以避免额外渲染,但是额外增加了代码复杂度。
内存消耗
由于 React.memo
通过缓存组件实例来避免不必要的重新渲染,这将消耗额外的内存。在某些情况下,这可能会成为问题。
正确姿势
为了正确地使用 React.memo
并最大化其潜在优势,建议遵循以下准则:
1. 性能诊断
在使用 React.memo
之前,首先应使用性能诊断工具(如 React DevTools)来确认组件是否确实存在不必要的渲染。
2. 简单组件优先
适用于那些渲染输出完全由 props 确定,并且没有副作用或依赖于全局状态的组件。
3. 明确目标
明确优化目标,如减少渲染次数、减少计算量等,以便更有针对性地应用 React.memo
。
4. 自定义比较函数
如果默认的浅比较不符合需求,可以传递一个自定义比较函数给 React.memo
。
1const areEqual = (prevProps, nextProps) => {
2 // 自定义比较逻辑
3 return prevProps.id === nextProps.id;
4};
5
6const MemoizedComponent = React.memo(MyComponent, areEqual);
5. 避免内联对象和函数
避免在渲染过程中创建内联对象和函数,因为这将导致 React.memo
的浅比较失败。
1// 避免这种做法
2<MemoizedComponent style={{ color: 'blue' }} onClick={() => doSomething()} />
6. 与其他优化手段配合
考虑 React.memo
与其他性能优化手段(如 useMemo
, useCallback
, PureComponent
等)的组合使用。
7. 测试与审查
在实施任何优化后,都应进行全面的功能和性能测试,确保没有引入新的问题或副作用。
8. 文档和代码注释
清晰地记录为何使用 React.memo
,以及你期望达到什么效果,这样能帮助团队其他成员理解其用途和限制。
通过遵循上述准则,你可以更加有效地利用 React.memo
进行性能优化,同时避免或最小化潜在的问题和副作用。
总而言之,越是简单越是纯粹的组件更适合 React.memo