介绍
React.memo 是 React 提供的一个高阶组件(Higher-Order Component, HOC),用于优化函数组件的渲染性能。它通过 浅比较 组件的 props 来决定是否重新渲染组件。如果 props 没有变化,组件将跳过渲染,直接复用上一次的渲染结果。 通俗点说就是当父组件重新渲染时,如果 memo 包装的子组件的 props 没有发生变化,React 将跳过这个子组件的渲染,直接复用上一次的渲染结果。这可以避免不必要的重新渲染,提高性能。 使用场景:
- 当组件经常接收相同的 props 但父组件频繁更新时
- 纯展示型组件,渲染开销较大时
- 需要避免子组件无谓渲染的场景
重新渲染 render 会做些什么?会对新旧 VNode 进行对比,也就是我们所说的 Dom diff。对新旧两棵树进行一个深度优先遍历,这样每一个节点都会一个标记,在到深度遍历的时候,每遍历到一和个节点,就把该节点和新的节点树进行对比,如果有差异就放到一个对象里面,遍历差异对象,根据差异的类型,根据对应对规则更新 VNode
基本用法
React.memo
接受一个函数组件作为参数,并返回一个记忆化(memoized)的组件。
const MemoizedComponent = React.memo(Component, arePropsEqual?);
Component
:需要优化的函数组件arePropsEqual
: (可选):自定义比较函数,用于决定是否重新渲染组件。默认情况下,React.memo 会对 props 进行浅比较。
案例
以下代码未使用React.memo
:
import React, { useState } from 'react';
function Child({ name }) {
console.log('Child rendered');
return <div>{name}</div>;
}
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={()=> setCount(count + 1)}>Increment</button>
<p>Count: {count}</p>
<Child name="Alice" />
</div>
);
}
export default Parent;
以上代码会出现什么问题呢?那就是每次点击按钮时,Parent
组件会重新渲染,导致 Child
组件也重新渲染,即使 name
没有变化。
下面我们就使用 React.memo 来进行优化:
import React, { useState, memo } from 'react';
const Child = memo(function Child({ name }) {
console.log('Child rendered');
return <div>{name}</div>;
});
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={()=> setCount(count + 1)}>Increment</button>
<p>Count: {count}</p>
<Child name="Alice" />
</div>
);
}
export default Parent;
- 效果:
Child
组件只在 name 变化时重新渲染。点击按钮时,Child
不会重新渲染。
自定义比较函数
默认情况下,React.memo
会对 props
进行浅比较。如果 props
是复杂对象或数组,浅比较可能无法满足需求。此时可以提供一个自定义的比较函数。
** 语法 **
const MemoizedComponent = React.memo(Component, (prevProps, nextProps) => {
// 返回 true 表示 props 相等,不重新渲染
// 返回 false 表示 props 不相等,重新渲染
});
示例:
import React, { useState, memo, useCallback } from 'react';
const Child = memo(
function Child({ user }) {
console.log('Child rendered');
return <div>{user.name}</div>;
},
(prevProps, nextProps) => {
// 只有当 user.name 变化时才重新渲染
return prevProps.user.name === nextProps.user.name;
}
);
注意事项
- 浅比较的限制:
React.memo
默认使用浅比较,如果props
是对象或数组,浅比较可能无法检测到内部变化。- 对于复杂
props
,建议使用自定义比较函数。
- 不要滥用
React.memo
:- 对于简单组件,
React.memo
可能不会带来明显的性能提升,反而会增加比较的开销, 因为比较 props 本身也需要计算开销,如果组件经常接收不同的 props,使用 memo 反而会增加额外开销 - 只有在组件渲染开销较大或 props 变化较少时,才建议使用 React.memo。
- 对于简单组件,
- 与
useMemo
和useCallback
结合使用:- 如果 props 包含函数或复杂对象,可以使用
useCallback
或useMemo
来避免不必要的重新创建。
- 如果 props 包含函数或复杂对象,可以使用
import React, { useState, memo, useCallback } from 'react';
const Child = memo(function Child({ onClick }) {
console.log('Child rendered');
return <button onClick={onClick}>Click Me</button>;
});
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('Button clicked');
}, []);
return (
<div>
<button onClick={()=> setCount(count + 1)}>Increment</button>
<p>Count: {count}</p>
<Child onClick={handleClick} />
</div>
);
}
export default Parent;
注意: React.memo 和 useMemo 虽然都用于优化性能,但它们的使用场景和作用有明显区别:React.memo 用于优化整个组件的重新渲染,useMemo 用于优化组件内部特定的计算结果或值
总结
React.memo
用于优化函数组件的渲染性能,避免不必要的重新渲染。- 默认情况下,它会对 props 进行浅比较。如果需要更复杂的比较逻辑,可以提供一个自定义比较函数。
- 结合
useCallback
和useMemo
可以进一步优化性能。