首页 / React 19 入门教程 / React Compiler 自动优化

React 19 入门教程

React Compiler 自动优化

本教程共 50 篇 · 第 46 篇 · 更新于 2026-07-29 · 约 8 分钟阅读

ReactCompiler记忆化性能优化useMemoReact 19

46. React Compiler 自动优化

本节目标:理解 React Compiler 如何替你自动记忆化,减少手动优化的需要。

为什么需要编译器

React 默认性能已经够快。但有些场景你需要手动”记忆化”来避免不必要的重渲染:

import { useMemo, useCallback, memo } from 'react';

const ExpensiveComponent = memo(function ExpensiveComponent({ data, onClick }) {
  const processedData = useMemo(() => {
    return expensiveProcessing(data);
  }, [data]);

  const handleClick = useCallback((item) => {
    onClick(item.id);
  }, [onClick]);

  return (
    <div>
      {processedData.map(item => (
        <Item key={item.id} onClick={() => handleClick(item)} />
      ))}
    </div>
  );
});

问题来了——handleClick 确实被 useCallback 包裹了。

() => handleClick(item) 每次渲染还是新建一个箭头函数,memo 的优化就这么被破了。

手动记忆化既繁琐又容易出错。React 编译器帮你自动搞定。

编译器做了什么

React Compiler 是构建时工具。它在编译阶段分析你的代码,自动插入等效的记忆化逻辑。

编译前:

function ExpensiveComponent({ data, onClick }) {
  const processedData = expensiveProcessing(data);

  const handleClick = (item) => {
    onClick(item.id);
  };

  return (
    <div>
      {processedData.map(item => (
        <Item key={item.id} onClick={() => handleClick(item)} />
      ))}
    </div>
  );
}

编译后(伪代码):

import { c as _c } from "react/compiler-runtime";

function ExpensiveComponent({ data, onClick }) {
  const $ = _c(2);                 // 分配缓存槽
  let processedData, handleClick;

  if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
    processedData = expensiveProcessing(data);
    $[0] = processedData;          // 缓存计算结果
  } else {
    processedData = $[0];
  }

  if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
    handleClick = (item) => { onClick(item.id); };
    $[1] = handleClick;            // 缓存函数
  } else {
    handleClick = $[1];
  }

  // ...渲染 JSX
}

编译器能正确处理箭头函数的问题,比手写更可靠。

安装与配置

安装

npm install -D babel-plugin-react-compiler@latest

Vite 配置(推荐)

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: ['babel-plugin-react-compiler'],
      },
    }),
  ],
});
Note

React 编译器必须在 Babel 插件管道中第一个运行,否则无法正确分析源码。

验证是否生效

打开 React DevTools,被优化的组件旁边会显示 “Memo ✨” 徽章。

编译器能优化什么

编译器主要针对两类场景:

1. 跳过组件的级联重渲染

父组件 state 变了,所有子组件默认都会重渲染。

编译器能分析出”只有依赖的部分需要重渲染”,自动跳过无关子树。

function FriendList({ friends }) {
  const onlineCount = useFriendOnlineCount();

  return (
    <div>
      <span>{onlineCount} 人在线</span>
      {friends.map(friend => (
        <FriendCard key={friend.id} friend={friend} />
      ))}
      <MessageButton />
    </div>
  );
}

编译后,friends 变化时 <MessageButton> 不会重新渲染。

onlineCount 变化时 <FriendCard> 列表不会被重建。

2. 缓存昂贵计算

function TableContainer({ items }) {
  // 这个调用会被自动缓存
  const data = expensivelyProcess(items);
  // ...
}
Warning

编译器只对 React 组件和 Hook 做记忆化,不处理普通函数。跨组件共享的缓存也不会自动处理。

不需要编译器的时候

某些场景下你想跳过编译。用 "use no memo" 指令:

function ProblematicComponent() {
  "use no memo";
  // 这里的代码不会被编译器优化
}

这应该是临时方案。找到根因后应该移除指令,让编译器处理。

与现有 useMemo 的关系

编译器不是来替代 useMemo 的——至少现在不是。

推荐做法:

  • 新项目:信任编译器,少写甚至不写 useMemo/useCallback
  • 老项目:保留现有的记忆化,移除它可能改变编译器的优化结果
  • 精确控制场景:如果 memoized 值被用作 effect 依赖,保留 useMemo
// 这个场景建议保留 useMemo
const options = useMemo(() => ({
  url: props.url,
  method: 'POST'
}), [props.url]);

useEffect(() => {
  fetchData(options);
}, [options]);  // options 引用稳定,effect 不会重复触发

ESLint 插件

React 编译器附带一条 ESLint 规则,帮你识别无法优化的代码:

npm install -D eslint-plugin-react-hooks@latest

ESLint 会标出违反 React 规则的组件。被标记的组件会被编译器跳过。

你不需要一次修完所有警告。按自己的节奏逐个修复即可。

渐进式采用

如果一次性全量上线不放心,有三种渐进策略:

1. 按目录编译

// babel.config.js
module.exports = {
  overrides: [
    {
      test: './src/modern/**/*.{js,jsx,ts,tsx}',
      plugins: ['babel-plugin-react-compiler']
    }
  ]
};

2. 选择加入模式

// babel.config.js
module.exports = {
  plugins: [
    ['babel-plugin-react-compiler', {
      compilationMode: 'annotation'
    }]
  ]
};

需要优化的组件手动加 "use memo"

function TodoList({ todos }) {
  "use memo";
  const sorted = todos.slice().sort();
  // ...
}

3. 运行时功能开关

// babel.config.js
module.exports = {
  plugins: [
    ['babel-plugin-react-compiler', {
      gating: {
        source: 'ReactCompilerFeatureFlags',
        importSpecifierName: 'isCompilerEnabled'
      }
    }]
  ]
};

可以做 A/B 测试,按用户分组逐步放量。

调试技巧

如果编译后行为不对:

  1. 在可疑组件加 "use no memo",看问题是否消失
  2. 检查是否违反 React 规则(用 ESLint)
  3. 尝试移除手动记忆化,看是否是两种优化冲突

编译器错误很少见。大多数问题是运行时违反 React 规则导致的。

Tip

在 React DevTools 里看”Memo ✨“徽章。没出现说明该组件被编译器跳过了。

小结

  • React Compiler 在构建时自动插入记忆化代码
  • 目标是减少手动使用 useMemouseCallbackReact.memo 的需要(某些场景仍需手动优化)
  • Vite 配置加 Babel 插件即可启用,React DevTools 验证
  • 渐进式采用支持按目录、选择加入、运行时开关三种策略
  • 调试时用 "use no memo" 定位问题组件