React `memo`、`useMemo`、`useCallback` 配合使用及注意事项

React memouseMemouseCallback 配合使用及注意事项

这三个 API 是 React 性能优化的核心工具,主要用于避免不必要的渲染减少重复计算。以下是它们的核心作用、配合方式及注意事项:


一、核心作用

API 作用
React.memo 缓存组件:当父组件重新渲染时,若子组件的 props 未变化,阻止子组件重新渲染。
useMemo 缓存计算结果:当依赖项未变化时,避免重复执行复杂计算(如过滤列表、复杂对象处理)。
useCallback 缓存函数引用:当依赖项未变化时,返回同一个函数实例,避免子组件因函数引用变化而重新渲染。

二、配合使用场景

1. React.memo + useCallback

场景:父组件传递函数给子组件,避免子组件因函数引用变化而重新渲染。

// 子组件:用 React.memo 包裹
const Child = React.memo(({ onClick }) => {
  console.log("子组件渲染");
  return <button onClick={onClick}>点击</button>;
});

// 父组件:用 useCallback 缓存函数
const Parent = () => {
  const [count, setCount] = useState(0);
  
  // ✅ 使用 useCallback 避免每次渲染生成新函数
  const handleClick = useCallback(() => {
    setCount(prev => prev + 1);
  }, []); // 无依赖

  return (
    <div>
      <Child onClick={handleClick} />
      <p>Count: {count}</p>
    </div>
  );
};

2. React.memo + useMemo

场景:父组件传递复杂对象或数组给子组件,避免因对象引用变化导致子组件重新渲染。

// 子组件:用 React.memo 包裹
const Child = React.memo(({ data }) => {
  console.log("子组件渲染");
  return <div>{data.value}</div>;
});

// 父组件:用 useMemo 缓存对象
const Parent = () => {
  const [count, setCount] = useState(0);
  
  // ✅ 使用 useMemo 缓存 data 对象
  const data = useMemo(() => ({ value: "固定值" }), []); // 依赖为空,对象只创建一次

  return (
    <div>
      <Child data={data} />
      <button onClick={() => setCount(count + 1)}>更新父组件</button>
    </div>
  );
};

3. useMemo + useCallback

场景:当函数依赖某个计算结果时,同时使用两者优化。

const App = () => {
  const [count, setCount] = useState(0);
  const [input, setInput] = useState("");

  // ✅ 缓存计算结果
  const filteredList = useMemo(() => {
    return largeList.filter(item => item.includes(input));
  }, [input]); // 依赖 input 变化时重新计算

  // ✅ 缓存函数
  const handleAdd = useCallback(() => {
    setCount(prev => prev + filteredList.length);
  }, [filteredList]); // 依赖 filteredList 变化

  return (
    <div>
      <input value={input} onChange={(e) => setInput(e.target.value)} />
      <button onClick={handleAdd}>添加</button>
    </div>
  );
};

三、注意事项

1. 避免过度优化

  • 优先考虑可读性:仅在性能瓶颈出现时使用这些 API。
  • 简单组件无需使用:轻量级组件优化可能得不偿失(缓存本身有开销)。

2. 正确处理依赖数组

  • 依赖缺失:可能导致缓存的值或函数引用过时。
  • 依赖冗余:可能导致频繁重新计算。
// ❌ 错误:依赖缺失
const handleClick = useCallback(() => {
  console.log(count); // count 始终为初始值
}, []); // 缺少 count 依赖

// ✅ 正确:添加依赖
const handleClick = useCallback(() => {
  console.log(count);
}, [count]);

3. React.memo 的局限性

  • 浅比较问题:默认使用浅比较(shallow compare),若传递对象或数组,需结合 useMemo
  • 自定义比较函数:可通过第二个参数自定义 props 比较逻辑。
const Child = React.memo(({ data }) => {/* ... */}, (prevProps, nextProps) => {
  return prevProps.data.id === nextProps.data.id; // 自定义比较
});

4. useMemo 的适用场景

  • 复杂计算:如数据过滤、排序、数学计算等。
  • 避免创建新对象:若依赖项未变,返回缓存的对象/数组。
// ❌ 错误:每次渲染生成新数组
const list = [{ id: 1 }, { id: 2 }];

// ✅ 正确:缓存数组
const list = useMemo(() => [{ id: 1 }, { id: 2 }], []);

5. useCallback 的替代方案

  • 函数式更新:在 setState 中使用函数式更新,可减少依赖项。
const handleClick = useCallback(() => {
  setCount(prev => prev + 1); // 无需依赖 count
}, []);

四、最佳实践

  1. 性能检测工具
    使用 React DevTools 的 Profiler 分析组件渲染次数,确认优化是否有效。

  2. 优先使用 React.memo
    对于频繁渲染的组件,优先尝试 React.memo,再结合 useMemo/useCallback

  3. 避免嵌套对象的浅比较问题

    // ❌ 错误:data 每次渲染都是新对象,即使值相同
    <Child data={{ id: 1 }} />
    
    // ✅ 正确:用 useMemo 缓存对象
    const data = useMemo(() => ({ id: 1 }), []);
    <Child data={data} />
    
  4. 函数传递优化

    // ❌ 错误:每次渲染生成新函数,导致 Child 重新渲染
    <Child onClick={() => { /* ... */ }} />
    
    // ✅ 正确:用 useCallback 缓存函数
    const handleClick = useCallback(() => { /* ... */ }, []);
    <Child onClick={handleClick} />
    

总结

  • React.memo:用于组件层级,避免因父组件渲染导致子组件无意义渲染。
  • useMemo:用于缓存计算结果或复杂对象,减少重复计算和引用变化。
  • useCallback:用于缓存函数引用,避免子组件因函数变化重新渲染。

三者配合使用时,需注意依赖数组的正确性和浅比较的局限性,避免过度优化。最终目标是在保证可维护性的前提下,提升关键路径的性能。

posted @ 2025-03-29 23:25  奔付山河  阅读(382)  评论(0)    收藏  举报