跳到主要内容

缓存计算与组件

什么时候值得缓存渲染中的计算?​

  • useMemo: useMemo(calculateValue, dependencies) 缓存纯计算的结果,依赖按 Object.is 逐项相等时可复用;
  • 适用条件: 计算确实昂贵、结果经常复用,或结果引用影响下游优化时才有价值;先测量生产场景而非只看开发重复调用;
  • 非语义保证: React 可能丢弃缓存,正确性必须在重新计算时仍成立;需要稳定记忆的数据应使用 state 或 ref;
const visibleTodos = useMemo(() => todos.filter((todo) => todo.tab === tab), [todos, tab]);

如何让回调引用配合子组件缓存?​

  • useCallback: 缓存函数本身而不是调用结果,依赖不变时给出同一函数引用,常配合 memo 子组件;
  • 依赖闭包: 所读响应式值仍要列入依赖,不能为了稳定引用保留旧数据;从旧状态计算时可使用更新函数;
const addOne = useCallback(() => setCount((count) => count + 1), []);

memo 可以跳过哪些组件更新?​

  • 默认比较: memo(Component) 按每个 prop 的 Object.is 结果判断是否可跳过父渲染引起的工作,不是深比较整个对象;
  • 保留更新: 组件自身 state 或读取的 Context 变化仍会更新,memo 不是冻结 UI 的工具;
  • 自定义比较: arePropsEqual 必须比较全部会影响输出与行为的 props,包括函数闭包;比较成本要低于重新渲染;
import { memo } from "react";

const List = memo(function List({ items }) {
return (
<ul>
{items.map((item) => (
<li key={item.id}>{item.text}</li>
))}
</ul>
);
});

为什么已经缓存了却仍然重新计算?​

  • 不稳定输入: 每次新建 options、数组或回调会使依赖失效,把计算所需构造移入 useMemo 或保持上游身份稳定;
  • 无效优化: 一项总在变化的 prop 就可能破坏整次 memo;先缩小状态范围,避免多余 Effect,再增加缓存;
  • 编译器关系: React Compiler 可以自动完成许多缓存,但只在实际启用且符合规则时生效,不能假定安装 React 就自动优化;