人人都会AI编程

React.memo:组件级 memo 化

更新时间:2026-07-11

React.memo 是一个高阶组件,用于阻止因父组件重渲染而导致的子组件不必要的重新渲染。它通过浅比较(shallow comparison)来判断组件的 Props 是否发生变化,如果 Props 没有变化,则复用上一次渲染的结果,跳过本次渲染。

为什么需要 React.memo

React 的默认行为是:当父组件重新渲染时,其内部的所有子组件都会递归地重新渲染。即使子组件接收的 Props 完全没有变化,也依然会触发渲染。这本身并非缺陷,但在大型应用中,频繁的无效渲染会累积成性能问题。

function Parent() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('Alice');

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <Child name={name} />  {/* 每次 count 变化,Child 都会重渲染 */}
    </div>
  );
}

function Child({ name }) {
  console.log('Child 渲染了');
  return <p>{name}</p>;
}

上述代码中,Childname 属性并未改变,但每次点击按钮更新 count 时,Child 都会重新渲染并打印日志。如果 Child 内部有复杂计算或渲染大列表,就会产生可感知的卡顿。

基本用法

React.memo 包裹目标组件即可:

const Child = React.memo(function Child({ name }) {
  console.log('Child 渲染了');
  return <p>{name}</p>;
});

现在,只有当 name 真的发生变化时,Child 才会重新渲染。父组件因 count 改变而重渲染时,Child 会直接复用上一次的结果,不会执行组件函数。

浅比较的工作原理

React.memo 默认对 Props 中的每个字段进行 Object.is 比较(相当于 ===)。对于原始类型(字符串、数字、布尔值),只要值相同就不会触发渲染;对于引用类型(对象、数组、函数),相同的引用才会被视为未变化。

这意味着:如果父组件每次渲染都会生成新的对象或函数,那么 React.memo 将形同虚设。

// ❌ 父组件每次渲染都传递一个新的对象,memo 失效
<Child style={{ color: 'red' }} />

// ❌ 父组件每次渲染都传递一个新的函数
<Child onClick={() => handleClick()} />

要解决这个问题,需要配合 useMemouseCallback 来稳定引用:

const style = useMemo(() => ({ color: 'red' }), []);
const handleClick = useCallback(() => { ... }, []);

<Child style={style} onClick={handleClick} />

自定义比较函数

如果默认的浅比较不满足需求,可以传入第二个参数——一个自定义比较函数。该函数返回 true 表示“Props 相等,不需要渲染”,返回 false 表示“Props 不相等,需要重渲染”。

const Child = React.memo(
  function Child({ user }) {
    return <p>{user.name}</p>;
  },
  (prevProps, nextProps) => {
    // 只比较 user.id,忽略其他字段
    return prevProps.user.id === nextProps.user.id;
  }
);

注意:自定义比较函数会增加比较成本,只在确实需要更精细的控制时才使用。大多数情况下,配合 useMemo/useCallback 已经足够。

适用场景与反模式

何时使用 React.memo:

  1. 纯展示组件:组件只根据 Props 渲染,内部没有状态和副作用。这类组件是 memo 化最理想的目标。
  2. 渲染成本高的组件:组件内部有复杂计算、大列表渲染或深层嵌套的子组件树。
  3. 在同一父组件下渲染大量相同子组件:例如长列表中的每一项,memo 化可以避免整个列表的重渲染。
  4. Props 变化不频繁:如果组件的 Props 变化频率远低于父组件渲染频率,memo 能有效消除无效渲染。

何时不应使用 React.memo:

  1. Props 频繁变化:如果组件的 Props 在每次父组件渲染时几乎都会变化,memo 的比较成本会白白浪费,甚至因为每次都要比较后再决定渲染,反而比直接渲染更慢。
  2. 组件本身极轻量:比如一个简单的 <div> 或只渲染几个字符的 <span>,memo 带来的收益微乎其微,反而增加代码复杂度。
  3. 依赖全局 ContextReact.memo 只监听 Props 变化,如果组件内部使用了 useContext,Context 值变化时组件依然会重渲染(Context 变化不受 memo 控制)。

与其他优化手段的配合

React.memo 是组件层级的优化,需要和 Hooks 层级优化一起使用才能发挥最大效果:

  • 状态内容提升:把变化频繁的状态下沉到子树中,减少顶层组件的重渲染扩散。
  • children 模式:利用 children 作为“免渲染”插槽,避免直接嵌套导致的渲染传播。
  • 虚拟列表:对于超长列表,结合 react-window 等方案,仅渲染可视区域。
// 状态下沉:把频繁变化的 count 封装在独立的计数器组件内
function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

// 稳定展示的列表组件,用 memo 包裹
const UserList = React.memo(function UserList({ users }) {
  return users.map(u => <div key={u.id}>{u.name}</div>);
});

function App() {
  return (
    <div>
      <Counter />
      <UserList users={users} />  {/* users 不变就不会重渲染 */}
    </div>
  );
}

实际性能验证

不要凭直觉进行优化,务必使用 React DevTools 的 Profiler 面板来测量渲染耗时和渲染次数。勾选“Highlight updates when components render”选项,观察哪些组件进行了不必要的重渲染,再有针对性地应用 React.memo。过早优化是万恶之源,但要懂得在性能瓶颈出现时使用正确的工具。

总结

React.memo 是组件级性能优化的直接手段,核心原理是通过 Props 浅比较跳过无变化的渲染。它的有效性依赖于稳定的 Props 引用,通常需要与 useMemouseCallback 协同工作。使用它要基于实际性能测量,优先优化渲染昂贵的组件,避免在轻量组件和频繁变化的组件上滥用。