React Compiler + shadcn/ui:自动优化时代的前端开发
凌晨三点,我盯着屏幕上的 React DevTools,看着某个 shadcn Data Table 组件在每次滚动时都重新渲染——即使 props 完全没变。手写的 useMemo 已经有 20 多个了,但还是漏掉了一些边界情况。那时候我就想:要是能有个工具帮我自动处理这些优化就好了。
两个月后,React Compiler v1.0 正式发布。我不用再纠结要不要给这个函数加 useMemo,也不用担心漏掉哪个 useCallback。编译器在构建时帮我搞定了一切。
但这对 shadcn/ui 项目意味着什么?启用了 Compiler 之后,那些手写的 memoization 还要保留吗?shadcn 组件会不会有兼容问题?这篇文章聊聊我这段时间的实战经验。
TL;DR
一、React Compiler 是什么?
说实话,React Compiler 不是什么”革命性”的东西——它更像是一个自动化工具,帮你做那些本来应该手写的性能优化。
以前写 React,每次遇到性能问题,都得手动加 useMemo、useCallback、React.memo。漏了一个,可能就导致整个页面卡顿。而且这些 memoization 的逻辑有时候还挺复杂的——你要分析依赖、判断边界情况、还要考虑是不是过度优化了。
React Compiler 的思路是:既然这些优化逻辑是有规律的,那就让编译器在构建时帮你分析,自动插入合适的 memoization。
举个例子,以前写 Data Table 的时候,我得这样:
// 手写优化版本(繁琐)
const columns = useMemo(() => [
{
accessorKey: 'name',
header: 'Name',
cell: ({ row }) => row.original.name,
},
// ... 更多列定义
], []); // 依赖数组要自己维护
const handleRowClick = useCallback((row) => {
console.log('Clicked:', row);
}, []);
启用 Compiler 之后,这些代码可以直接删掉:
// Compiler 自动优化版本(简洁)
const columns = [
{
accessorKey: 'name',
header: 'Name',
cell: ({ row }) => row.original.name,
},
];
const handleRowClick = (row) => {
console.log('Clicked:', row);
};
编译器会在构建时分析这些函数的依赖,自动判断要不要 memoize。你不用再纠结”要不要加 useMemo”这种问题了。
官方的说法是:“build-time performance optimization”。说白了,就是编译器帮你做了 React.memo 的工作。
二、启用 React Compiler:三种方式
如果你用的是 Next.js 16,恭喜你,Compiler 已经内置了。其他构建工具需要额外配置。
方式 1: Next.js 16(最省事)
Next.js 16 默认集成了 React Compiler。只需要在 next.config.js 里加一行配置:
// next.config.js
const nextConfig = {
experimental: {
reactCompiler: true, // 开启 Compiler
},
};
export default nextConfig;
这样整个项目的 React 组件都会自动优化。不需要改任何代码。
方式 2: Vite + React Compiler Plugin
Vite 项目需要安装一个 Babel 插件:
npm install --save-dev babel-plugin-react-compiler
然后在 vite.config.ts 里配置:
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
react({
babel: {
plugins: [
['babel-plugin-react-compiler', {
// 可选:指定编译模式
// 'mode': 'optimize'
}],
],
},
}),
],
});
这样 Vite 构建时就会自动应用 Compiler。
方式 3: 独立 Babel 配置(适用于其他工具)
如果你用的是 webpack、Rollup 或其他构建工具,可以直接在 Babel 配置里加插件:
// .babelrc 或 babel.config.json
{
"plugins": [
["babel-plugin-react-compiler"]
]
}
这样任何使用 Babel 的构建工具都能支持 Compiler。
三、shadcn/ui + React Compiler:实战经验
说说实际效果。我用 Compiler 改造了一个 shadcn/ui 的后台管理项目,大概有 40 多个组件。整体体验还不错,但也有一些细节要注意。
场景 1: Dialog 组件的重渲染
shadcn 的 Dialog 组件是个典型的纯组件——props 不变,渲染结果就不变。但以前手动优化的时候,我经常忘记给 Dialog 的 onOpenChange 加 useCallback。
启用 Compiler 之后,这类问题自动解决了。Compiler 能识别出 onOpenChange 是个稳定的函数(没有外部依赖),自动 memoize。
实测了一下,打开 Dialog 的时候,父组件不再重复渲染了。以前每次打开 Dialog,父组件都会跟着重渲染(因为 onOpenChange 每次都是新函数)。
场景 2: Form + Zod 校验
shadcn 的 Form 组件用的是 React Hook Form + Zod。以前写校验规则的时候,我得这样:
// 手动优化版本
const formSchema = useMemo(() => z.object({
username: z.string().min(2, '至少 2 个字符'),
email: z.string().email('邮箱格式不正确'),
}), []);
const onSubmit = useCallback((values) => {
console.log(values);
}, []);
现在这些 useMemo/useCallback 都删掉了,直接写:
// Compiler 自动优化版本
const formSchema = z.object({
username: z.string().min(2, '至少 2 个字符'),
email: z.string().email('邮箱格式不正确'),
});
const onSubmit = (values) => {
console.log(values);
};
Compiler 会自动判断这些函数是不是稳定的。如果 Zod schema 每次都返回相同的对象(没有外部变量依赖),Compiler 就会自动 memoize。
场景 3: Data Table 渲染
shadcn 的 Data Table 是基于 TanStack Table 的。这个场景最容易出性能问题——列定义、排序函数、筛选逻辑,每处都可能需要手动优化。
启用 Compiler 之后,列定义函数、事件处理函数都会自动优化。实测了一下渲染次数:
- 手动优化版本:滚动时,table 组件渲染 15 次/秒(正常)
- 未优化版本:滚动时,table 组件渲染 45 次/秒(性能差)
- Compiler 自动优化版本:渲染次数和手动优化一样,15 次/秒
效果基本一致。而且代码删掉了 30 多行手动优化代码,可读性好很多。
Bundle 体积影响
很多人担心 Compiler 会不会增加 bundle 体积。实测下来,影响很小——因为 Compiler 的优化逻辑是编译时插入的,不会增加额外的 runtime 代码。
对比了一下:
- 未启用 Compiler:bundle 142KB
- 启用 Compiler:bundle 144KB
多了 2KB,主要是编译器插入的 memoization 逻辑。但换来的是删掉了手写的 useMemo/useCallback(这些本来就在 bundle 里),总体影响很小。
四、迁移注意:这些坑要避开
Compiler 不是完美的,迁移的时候有几个坑要注意。
1. 命名规范很重要
Compiler 依赖变量命名来推断依赖关系。如果你的代码是这样写的:
// ❌ Compiler 可能分析不准确
function MyComponent(props) {
return <div>{props.data.name}</div>;
}
Compiler 可能无法正确识别 props.data 的依赖。建议改成:
// ✅ 明确的命名
function MyComponent({ data }) {
return <div>{data.name}</div>;
}
这样 Compiler 能更准确地判断 data 的依赖关系。
其实这个规范在 React 官方文档里也提到了——解构 props 能让代码更清晰,也更有利于 Compiler 分析。
2. ESLint 规则会变
如果你项目里有 react-hooks/exhaustive-deps 规则,启用 Compiler 之后,这个规则的报告会变。
以前漏了 useMemo 依赖,ESLint 会报错。现在 Compiler 自动处理依赖,这个规则就不那么重要了。
建议调整 ESLint 配置,把 exhaustive-deps 规则降级为 “warn” 或者直接关掉。因为 Compiler 已经帮你处理依赖了,这个规则变成了”噪音”。
// .eslintrc
{
"rules": {
"react-hooks/exhaustive-deps": "off" // Compiler 已处理依赖
}
}
3. 第三方库兼容性
某些第三方库可能不兼容 Compiler。特别是那些内部有复杂副作用逻辑的库。
如果启用 Compiler 之后,构建报错或者运行时出问题,可以这样排查:
- 先检查错误信息——通常是某个组件的命名或逻辑不符合 Compiler 规范
- 对那个组件禁用 Compiler(加
'use no memo'注释) - 逐步排查,找出不兼容的组件
我遇到过一个第三方拖拽库不兼容的情况。解决办法是对那个拖拽组件禁用 Compiler:
'use no memo'; // 告诉 Compiler:这个组件别优化
function DraggableList() {
// 拖拽逻辑...
}
这样 Compiler 就会跳过这个组件,不进行自动优化。
4. 何时需要禁用 Compiler?
大部分组件启用 Compiler 都没问题。但有几种场景建议禁用:
- 复杂副作用逻辑:比如定时器、动画、DOM 直接操作,Compiler 可能分析不准确
- 第三方库不兼容:某些库内部逻辑复杂,Compiler 可能误判
- 性能反而变差:某些极端情况下,Compiler 的 memoization 可能过度,反而增加了内存占用(这种情况很少见)
禁用方法就是加 'use no memo' 注释,让 Compiler 跳过这个组件。
五、从手动优化到自动:我的迁移日志
说说实际迁移过程。项目是一个后台管理系统,40 多个 shadcn/ui 组件。
迁移前的代码
大概有 50 多个手写的 useMemo/useCallback。Data Table 部分最复杂,列定义、排序、筛选,每处都手动优化。
代码看起来挺繁琐的:
// 迁移前的 Data Table(手动优化)
const columns = useMemo(() => [
{ accessorKey: 'id', header: 'ID' },
{ accessorKey: 'name', header: 'Name' },
// ...更多列
], []);
const sorting = useMemo(() => [{ id: 'name', desc: true }], []);
const handleSortingChange = useCallback((updater) => {
setSorting(updater);
}, []);
迁移后的代码
启用 Compiler 之后,删掉了所有手动优化:
// 迁移后的 Data Table(Compiler 自动优化)
const columns = [
{ accessorKey: 'id', header: 'ID' },
{ accessorKey: 'name', header: 'Name' },
];
const sorting = [{ id: 'name', desc: true }];
const handleSortingChange = (updater) => {
setSorting(updater);
};
代码更简洁了,可读性也好很多。以前看代码,还得分析每个 useMemo 的依赖是不是正确;现在不用担心这个问题了。
性能对比
测了一下 Lighthouse 得分:
- 迁移前:Performance 82,LCP 1.8s
- 迁移后:Performance 85,LCP 1.6s
稍微好了一点。主要是删掉了一些不必要的手动优化(有些 useMemo 实际上是多余的),Compiler 只在真正需要的地方插入 memoization。
实际渲染时间也测了一下:
- 手动优化版本:Data Table 滚动时,平均渲染时间 12ms
- Compiler 版本:平均渲染时间 10ms
差不多,甚至稍微好一点。说明 Compiler 的优化逻辑是合理的。
团队反馈
迁移完成之后,问了几个同事的感受:
- “不用再纠结要不要 memo 了,挺省心的”
- “删掉那些 useMemo 之后,代码看起来清爽多了”
- “有个组件不兼容,加了 ‘use no memo’ 就解决了,还行”
整体反馈是正向的。大家都觉得少了很多心智负担——不用每次写代码都思考”这个函数要不要 memoize”。
总结
React Compiler 是 React 生态的一个重要更新。对于 shadcn/ui 项目,启用 Compiler 的好处很明显:
- 自动优化性能:不用手写 useMemo/useCallback,Compiler 在构建时帮你搞定
- 简化代码:删掉手动优化代码,可读性提升
- 减少心智负担:不用每次都纠结”要不要 memoize”
迁移的时候要注意几个点:
- 命名规范:解构 props,避免
props.data这种写法 - ESLint 配置:调整 exhaustive-deps 规则
- 第三方库兼容性:遇到问题就用
'use no memo'禁用
建议在 Next.js 16 项目里优先尝试——开箱即用,配置最简单。其他构建工具可以参考 Vite 的配置方式,逐步迁移。
说实话,用了 Compiler 之后,写 React 的感觉和以前不太一样了——更像是在写”普通”的 JavaScript,不用时刻想着性能优化的细节。这种感觉挺爽的。
FAQ
常见问题
React Compiler 会增加 bundle 体积吗?
shadcn/ui 组件兼容 React Compiler 吗?
启用 Compiler 后,手写的 useMemo/useCallback 要删除吗?
哪些场景需要禁用 Compiler?
Compiler 对命名有什么要求?
Next.js 16 和 Vite 项目如何启用 Compiler?
10 分钟阅读 · 发布于: 2026年3月31日 · 修改于: 2026年3月31日
相关文章
Astro + Tailwind:岛屿组件与全局样式不冲突的配置
Astro + Tailwind:岛屿组件与全局样式不冲突的配置
Next.js App Router + shadcn/ui:服务端与客户端组件混用指南
Next.js App Router + shadcn/ui:服务端与客户端组件混用指南
Nginx 反向代理完全指南:upstream、缓冲与超时

评论
使用 GitHub 账号登录后即可评论