React 性能优化指南

别瞎优化,先用 DevTools 找到瓶颈

核心思路

React 性能问题就一个核心:减少不必要的重渲染

1. 避免不必要的重渲染

把 State 往下移

如果父组件有个频繁变化的 State,但只有一小块 UI 用到了它,就把这块 UI 拆成子组件:

// ❌ 父组件任何 State 变化,子组件都会重渲染
function Parent() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <ExpensiveComponent /> // 用的不是 count,但也跟着重渲染
      <button onClick={() => setCount((c) => c + 1)}>{count}</button>
    </div>
  );
}

// ✅ 把 State 和用到它的组件放一起
function Parent() {
  return (
    <div>
      <ExpensiveComponent />
      <Counter />
    </div>
  );
}

function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount((c) => c + 1)}>{count}</button>;
}

React.memo

对不常变化的子组件用 React.memo

// 只有 props 变了才会重渲染
const MyComponent = React.memo(function MyComponent({ name }) {
  return <div>{name}</div>;
});

memo 做的是浅比较,props 没变就跳过渲染。

2. 保持引用稳定

如果用了 memo,但 props 里有函数或对象,还得保证它们不变:

useCallback — 缓存函数

const handleClick = useCallback(() => {
  console.log(count);
}, [count]); // 只有 count 变才创建新函数

useMemo — 缓存对象/计算结果

const config = useMemo(
  () => ({
    theme: "dark",
    language: "zh",
  }),
  [],
); // 依赖空数组,永远返回同一个对象

3. 列表优化

Key 要用唯一 ID

详见key 到底有什么用?

虚拟列表

数据几千条?不要全渲染。用 react-windowreact-virtualized,只渲染可视区域那十几条。

4. Context 优化

Context 的 value 变了,所有消费者都会重渲染

拆分成多个 Context

// ❌ 什么都在一个 Context 里
const AppContext = createContext();

// ✅ 按业务拆分
const ThemeContext = createContext();
const UserContext = createContext();

value 用 useMemo 缓存

<AppContext.Provider value={useMemo(() => ({
  user,
  setUser
}), [user])}>

5. 懒加载

首屏不用的组件,延迟加载:

const HeavyChart = React.lazy(() => import("./HeavyChart"));

function App() {
  return (
    <Suspense fallback={<div>加载中...</div>}>
      <HeavyChart />
    </Suspense>
  );
}

怎么知道哪里慢?

React DevTools Profiler

  1. 打开 DevTools -> Profiler
  2. 点击录制
  3. 做交互
  4. 看火焰图,哪个组件红色最长就是哪个慢

勾选 "Record why each component rendered",可以看到重渲染的原因。

总结

优化手段适用场景
State 下移某个 State 变化导致大片组件重渲染
React.memo子组件 props 不常变
useCallback/useMemo配合 memo 使用,防止 props 变
拆分 Context某个值变化导致无关组件重渲染
虚拟列表长列表(千条以上)
React.lazy首屏不用的组件

记住:优化之前先 Profiler,找到瓶颈再动手,别凭感觉瞎优化。