FORMA

运行时优化

减少不必要的组件重渲染与昂贵计算。构建侧见 build

核心概念

React 的重渲染规则很简单:父组件重渲染,默认所有子组件都会重渲染(不管 props 是否变化),除非子组件被 memo 包裹且 props 浅比较相等。性能优化的本质是在这个默认行为上做「减法」:减少触发渲染的次数、减少每次渲染需要重新计算的内容、减少需要渲染的 DOM 节点数。

避免不必要的重渲染

手段说明
状态下沉 / 提升把 state 放在真正需要的子树根部
Context 拆分高频与低频数据分 Context
memo + 稳定 propsmemo Hook
选择器订阅react-redux / Zustand 细粒度选取 state
children 作为组件而非渲染函数避免每次渲染都创建新的内联组件定义

示例:状态下沉

tsx
// 不佳:count 变化会连带整个 Page 重渲染,包括不相关的 Header/Footer
function Page() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <Header />
      <button onClick={() => setCount((c) => c + 1)}>{count}</button>
      <Footer />
    </div>
  );
}

// 更好:把 count 状态与它的 UI 收敛到独立组件
function Page() {
  return (
    <div>
      <Header />
      <Counter />
      <Footer />
    </div>
  );
}
function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount((c) => c + 1)}>{count}</button>;
}

HeaderFooter 不再因为 count 变化而重渲染——它们甚至不知道 count 的存在。

示例:useCallback 配合 memo

tsx
const ExpensiveRow = memo(function ExpensiveRow({
  item,
  onSelect,
}: { item: Item; onSelect: (id: string) => void }) {
  console.log("render row", item.id);
  return <li onClick={() => onSelect(item.id)}>{item.name}</li>;
});

function List({ items }: { items: Item[] }) {
  const [selectedId, setSelectedId] = useState<string | null>(null);

  // 若不用 useCallback,每次 List 渲染都会创建新函数引用,
  // 使 memo 的浅比较失效,ExpensiveRow 依然会重渲染
  const handleSelect = useCallback((id: string) => setSelectedId(id), []);

  return (
    <ul>
      {items.map((item) => (
        <ExpensiveRow key={item.id} item={item} onSelect={handleSelect} />
      ))}
    </ul>
  );
}

useCallback/useMemo 本身不是「万能加速器」,只有配合 memo 让下游组件真正跳过渲染时才有意义;滥用反而增加代码复杂度和一点点内存/比较开销。

列表与虚拟滚动

长列表(数千行)应使用虚拟滚动(只渲染可视区域 DOM),例如:

tsx
import { useVirtualizer } from "@tanstack/react-virtual";

function VirtualList({ items }: { items: Item[] }) {
  const parentRef = useRef<HTMLDivElement>(null);
  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 48,
  });

  return (
    <div ref={parentRef} style={{ height: 480, overflow: "auto" }}>
      <div style={{ height: virtualizer.getTotalSize(), position: "relative" }}>
        {virtualizer.getVirtualItems().map((row) => (
          <div
            key={row.key}
            style={{ position: "absolute", top: row.start, height: row.size, width: "100%" }}
          >
            {items[row.index].name}
          </div>
        ))}
      </div>
    </div>
  );
}

避免一次性渲染全部节点;DOM 节点数从「数据总量」降为「可视区域数量」,滚动性能不再随数据量线性下降。

昂贵计算

  • useMemo 缓存过滤/排序结果。
  • startTransition 降低筛选等更新的优先级(见 渲染)。
  • Web Worker 处理重 CPU 任务(与 React 状态更新分离):
ts
// worker.ts
self.onmessage = (e) => {
  const result = heavyCompute(e.data);
  self.postMessage(result);
};
tsx
function useHeavyCompute(input: Input) {
  const [result, setResult] = useState<Result | null>(null);
  useEffect(() => {
    const worker = new Worker(new URL("./worker.ts", import.meta.url));
    worker.postMessage(input);
    worker.onmessage = (e) => setResult(e.data);
    return () => worker.terminate();
  }, [input]);
  return result;
}

开发期分析

  • React DevTools Profiler:录制交互,查看组件渲染次数与耗时,定位「哪个组件渲染了但其实不需要」。
  • 结合浏览器 Performance 面板定位长任务(Long Task),尤其是主线程被单次渲染占用超过 50ms 的情况。

最佳实践

  • 先用 Profiler 测量再优化,不要凭直觉给所有组件加 memomemo 本身有比较开销,对频繁 props 变化的组件反而无益)。
  • 内联传给子组件的对象/数组/函数 props(如 style={{ color: 'red' }})每次渲染都是新引用,会让 memo 失效,需要 useMemo/useCallback 或提到组件外部。
  • 大列表虚拟化优先于「优化每一行渲染速度」——本质问题往往是渲染节点太多,不是节点渲染太慢。
  • 高频更新(拖拽、动画进度)尽量脱离 React 状态循环,用 ref 直接操作 DOM 或搭配 CSS/Web Animations API。
  • 避免过早抽象「性能优化 Hook」,多数中后台系统的瓶颈是网络请求而非渲染,先用 Profiler 确认瓶颈位置。

常见坑

现象常见原因处理
加了 memo 组件还是每次都重渲染传入了每次都新建的对象/函数 props配合 useMemo/useCallback 稳定引用
列表滚动卡顿未虚拟化、每行组件较重引入 TanStack Virtual / react-window
输入框卡顿每次按键都触发昂贵的同步计算startTransition 降低非紧急更新优先级
Context 消费方全部重渲染单一大 Context 塞了高频与低频数据拆分多个 Context,或引入选择器式状态库
useMemo/useCallback 用了却没效果依赖数组不稳定,或下游组件本身没做 memo检查依赖数组与子组件是否配合 memo

延伸阅读

参考文献

以下链接在编写时均可正常访问:

资料说明
React:性能优化官方建议
React DevTools开发者工具
web.dev:Optimize INP交互延迟(英文)
TanStack Virtual虚拟滚动
React:memoAPI

Series

performance

2 / 2