聊天讨论 别再写 useMemo 了——2026 年这 5 个 React 性能优化已经是反模式

193577746(kyriewen) · July 20, 2026 · 24 hits

打开任何一个 AI 辅助开发的 React 项目,你大概率会看到同样的画面:useMemo、useCallback、React.memo 铺天盖地,仿佛不写就会崩。

但 2026 年了,React Compiler 已经正式落地。你手动写的那些"优化",很多不但没用,还在拖慢你的代码。

先说结论:为什么要改

React Compiler(随 React 19 正式推出)做了一件事:在编译阶段自动分析组件,在表达式级别做细粒度 memoization。

这意味着:你手写的 useMemo、useCallback、React.memo——编译器都能自动做,而且做得更细、更准确。

手动写的问题在于:

维度 手动优化 React Compiler
粒度 组件/Hook 级别 表达式级别(更细)
准确性 依赖数组经常写错 编译器分析,不会遗漏
代码量 多 30-50% 包装代码 零额外代码
维护成本 依赖变了要手动更新 自动追踪
出错概率 依赖数组写错=缓存永远不更新 不存在这个问题

ESLint React v5.3 已经加了 no-unnecessary-use-memono-unnecessary-use-callback 规则——官方工具链都在告诉你别写了。

下面逐个看 5 个已经变成反模式的"优化"。

反模式 1:useMemo 包裹所有计算

最常见的 AI 生成代码模式:

function UserList({ users, filter }) {
  const filteredUsers = useMemo(() => {
    return users.filter(u => u.name.includes(filter));
  }, [users, filter]);

  const sortedUsers = useMemo(() => {
    return [...filteredUsers].sort((a, b) => a.name.localeCompare(b.name));
  }, [filteredUsers]);

  const stats = useMemo(() => ({
    total: sortedUsers.length,
    active: sortedUsers.filter(u => u.active).length,
  }), [sortedUsers]);

  return <div>{/* ... */}</div>;
}

三个 useMemo 做了一件事:过滤 + 排序 + 统计。

React Compiler 会自动分析这段代码的依赖关系,在usersfilter没变的时候跳过所有计算。你手写三个 useMemo,其实在做编译器已经做了的事,同时还增加了:

  • 三个依赖数组的维护成本
  • 三个闭包的内存开销
  • 让代码可读性下降一半

2026 年的写法:

function UserList({ users, filter }) {
  const sorted = users
    .filter(u => u.name.includes(filter))
    .sort((a, b) => a.name.localeCompare(b.name));

  const stats = {
    total: sorted.length,
    active: sorted.filter(u => u.active).length,
  };

  return <div>{/* ... */}</div>;
}

干净、直接、编译器自动优化。

唯一例外: 如果你的计算确实很重(比如对 10 万条数据做复杂聚合),并且你能用React DevTools Profiler证明它是瓶颈——那保留 useMemo。但 99% 的场景不是这样。

反模式 2:useCallback 包裹所有传给子组件的函数

function Dashboard() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  const handleReset = useCallback(() => {
    setCount(0);
  }, []);

  const handleExport = useCallback(() => {
    exportData(count);
  }, [count]);

  return (
    <>
      <Button onClick={handleClick} />
      <Button onClick={handleReset} />
      <ExportButton onClick={handleExport} />
    </>
  );
}

这段代码有三个问题:

  1. useCallback 本身不阻止子组件重渲染——除非子组件用了React.memo
  2. 没有 React.memo 的情况下,useCallback 做的事就是"稳定引用"——但 React Compiler 已经能自动判断哪些组件需要跳过
  3. handleExport的依赖数组里有count,每次 count 变化它都会重建——useCallback 完全没起作用

更直接的问题是:AI 工具特别爱生成 useCallback。 因为训练数据里大量"React 性能优化最佳实践"文章都在教"传给子组件的函数一定要 useCallback"。这个建议在 2023 年可能有道理,在 2026 年就是噪音。

2026 年的写法:

function Dashboard() {
  const [count, setCount] = useState(0);

  return (
    <>
      <Button onClick={() => setCount(c => c + 1)} />
      <Button onClick={() => setCount(0)} />
      <ExportButton onClick={() => exportData(count)} />
    </>
  );
}

反模式 3:React.memo 包裹每个导出组件

const UserCard = React.memo(function UserCard({ user, onSelect }) {
  return (
    <div onClick={() => onSelect(user.id)}>
      <Avatar src={user.avatar} />
      <span>{user.name}</span>
    </div>
  );
});

React.memo 的逻辑是:props 没变就跳过渲染。问题是:

  1. React Compiler 已经在做同样的事,而且粒度更细——它可以跳过组件内部的部分表达式,不需要跳过整个组件
  2. React.memo*每次渲染都要做 props 浅比较*——如果 props 经常变,这个比较本身就是白白浪费
  3. 和 useCallback 配套才有意义——但如果你已经不写 useCallback 了,React.memo 也没必要了

一条很好判断的规则: 如果你的组件渲染成本低于 props 浅比较成本(大部分简单组件都是这样),React.memo 就是负优化。

2026 年的写法: 直接导出,不包裹。

function UserCard({ user, onSelect }) {
  return (
    <div onClick={() => onSelect(user.id)}>
      <Avatar src={user.avatar} />
      <span>{user.name}</span>
    </div>
  );
}

反模式 4:useEffect + useState 做数据获取

这可能是最根深蒂固的反模式,也是 AI 生成代码最爱写的模式:

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelled = false;
    setLoading(true);

    fetchUser(userId)
      .then(data => {
        if (!cancelled) {
          setUser(data);
          setLoading(false);
        }
      })
      .catch(err => {
        if (!cancelled) {
          setError(err);
          setLoading(false);
        }
      });

    return () => { cancelled = true; };
  }, [userId]);

  if (loading) return <Spinner />;
  if (error) return <ErrorMessage error={error} />;
  return <div>{user.name}</div>;
}

25 行代码做一件事:获取数据。 而且还有这些隐患:

问题 后果
竞态条件 userId 快速变化时,旧请求覆盖新数据
瀑布请求 父组件获取完→子组件才开始获取→孙组件再等
无缓存 同一个 userId 每次挂载都重新请求
首次渲染闪白屏 loading 状态永远要经历 true→false
服务端渲染不友好 useEffect 在 SSR 阶段不执行

2026 年的写法(用 TanStack Query):

function UserProfile({ userId }) {
  const { data: user, isPending, error } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
  });

  if (isPending) return <Spinner />;
  if (error) return <ErrorMessage error={error} />;
  return <div>{user.name}</div>;
}

或者用 React 19 的use

function UserProfile({ userPromise }) {
  const user = use(userPromise);
  return <div>{user.name}</div>;
}

从 25 行到 3 行。 自动处理竞态、缓存、去重、后台刷新。

这不是风格偏好,是架构级的差距。 useEffect 做数据获取在 React 官方文档里已经被标注为不推荐。

反模式 5:过度拆分组件"优化性能"

我见过的一个极端案例:一个表单页面被拆成了 47 个组件——每个输入框一个组件、每个标签一个组件、每个校验提示一个组件。理由是"避免整个表单重渲染"。

这是对 React 渲染机制的误解。

组件拆分不等于性能优化。每多一个组件,React 就多了:

  • 一次 reconciliation 比较
  • 一个 Fiber 节点
  • 一次 props 序列化和比较

合理的拆分标准应该是:

场景 该拆 不该拆
组件超过 200 行 ✅ 可维护性 -
需要独立复用 ✅ 复用性 -
有独立的状态逻辑 ✅ 关注点分离 -
"这一块可能会频繁更新" ❌ 过早优化 ✅ 让 Compiler 处理
"拆小一点性能好" ❌ 错误假设 ✅ 用 Profiler 验证

真正该做的性能优化是状态下沉(state co-location): 把 state 放到真正需要它的组件里,而不是提升到父组件然后靠拆分来避免重渲染。

// ❌ 状态提升 + 过度拆分
function Form() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  return (
    <>
      <NameInput value={name} onChange={setName} />
      <EmailInput value={email} onChange={setEmail} />
    </>
  );
}

// ✅ 状态下沉,各管各的
function Form() {
  return (
    <>
      <NameInput />
      <EmailInput />
    </>
  );
}

function NameInput() {
  const [name, setName] = useState('');
  return <input value={name} onChange={e => setName(e.target.value)} />;
}

速查表:2026 年 React 性能优化决策

以前的"最佳实践" 2026 年的做法 什么时候还需要旧写法
useMemo 包裹计算 直接写,Compiler 处理 10 万 + 数据量的复杂聚合,且 Profiler 证实是瓶颈
useCallback 包裹函数 直接内联 传给第三方库且该库依赖引用稳定性
React.memo 包裹组件 直接导出 极重的渲染组件(Canvas/3D),且 Profiler 证实
useEffect 获取数据 TanStack Query / use() / SWR 永远不需要(没有例外)
过度拆分组件 按逻辑拆分 + 状态下沉 永远不需要(没有例外)
手动 shouldComponentUpdate 删掉 2026 年了不应该还有 Class 组件

判断标准就一条:先用 React DevTools Profiler 跑一遍。没有证据证明是瓶颈的,就别优化。

升级 React Compiler 的最小步骤

如果你的项目还没启用 React Compiler:

npm install react-compiler-runtime
npm install -D babel-plugin-react-compiler

Babel 配置加一行:

{
  "plugins": [
    ["babel-plugin-react-compiler", {}]
  ]
}

Vite 用户:

// vite.config.js
import { reactCompiler } from 'react-compiler-runtime/vite';

export default {
  plugins: [reactCompiler()],
};

启用后,可以逐步删掉不必要的 useMemo/useCallback/React.memo。不用一次性全删——编译器和手动优化可以共存,只是手动的部分变成了冗余代码。

别让 AI 替你做决定

这篇文章本质上在说一件事:不要因为 AI 生成了 useMemo,你就不敢删它。

AI 代码生成器的训练数据里充满了 2020-2024 年的"最佳实践"文章。那些文章在当时是对的——React 没有编译器,手动 memoization 是唯一选择。但 2026 年了,工具变了,实践也要变。

用 AI 写代码没问题。但 Review 的时候,你需要知道什么该留、什么该删。

你的项目升级 React Compiler 了吗?升级后删了多少行 useMemo?评论区聊聊。

No Reply at the moment.
You need to Sign in before reply, if you don't have an account, please Sign up first.