Appearance
扒了一下 Vercel 内部的 React 优化清单,这 4 个“性能杀手”最容易被忽视
最近闲逛 GitHub,偶然翻到了 Vercel 一个叫 agent-skills 的仓库。这其实是他们给 AI 喂的“知识库”,教 AI 怎么写出符合 Next.js 标准的代码。
仔细读完我发现,这简直就是一份 React 性能优化的“避坑指南”。里面列出的很多问题,甚至连不少两三年的老手都在犯。
我挑了其中几个对性能影响最大、也最容易中招的点,结合实际场景聊聊。
一、 习惯性 await 导致的“瀑布流”
在 Server Component 里写异步代码太爽了,以至于我们经常顺手就写出一串串行请求。Vercel 在文档里把这个标记为 CRITICAL(严重) 级别,因为它直接决定了首屏响应速度。
** 常见的“顺手”写法** 这种写法很符合直觉:先拿用户,再拿文章。但问题是,获取文章其实根本不需要等用户信息回来。
tsx
async function Page({ params }) {
// 这里卡住了!
const user = await fetchUser(params.id);
// 上面不结束,这里永远不开始
const posts = await fetchPosts(params.id);
return <Profile user={user} posts={posts} />;
}真正的并行处理 用 Promise.all 大家都知道,但在业务逻辑复杂时很容易忘。记住:只要两个请求没依赖关系,就别让它们排队。
tsx
async function Page({ params }) {
// 并发请求,耗时取决于最慢的那个接口
const [user, posts] = await Promise.all([
fetchUser(params.id),
fetchPosts(params.id)
]);
return <Profile user={user} posts={posts} />;
}进阶技巧:如果某个组件(比如评论区)特别慢,千万别在顶层 await。直接把 Promise 传给子组件,配合 Suspense 做流式渲染,让页面先展示出来,别为了一个评论区把整个页面卡白屏。
看起来是简化,但实际是麻烦
你肯定写过这样的代码:在 components/index.ts 里把所有组件导出来,引用时一行搞定。
这种写法看着很舒服(洁癖党最爱):
tsx
// components/index.ts
export * from './Button';
export * from './Modal';
export * from './Chart'; // 假设这个库巨大
// 页面引用
import { Button } from '@/components';后果:虽然你只用了 Button,但构建工具(特别是某些配置下的 Webpack)可能会把 Chart 相关的依赖也打包进去,导致 Tree-shaking 失效。
[正确] Vercel 的建议非常直接:
tsx
// 哪怕麻烦一点,也请直接引用文件
import Button from '@/components/Button';
import Modal from '@/components/Modal';这在大型项目中对 Bundle Size 的影响非常惊人。如果你觉得路径难写,不如通过配置 IDE 的自动导入来解决,而不是牺牲性能。
RSC 数据传输:别把数据库直接扔给前端
在 App Router 模式下,Server Component 传给 Client Component 的所有 props 都需要经过序列化。
这是一个极其容易被忽视的问题。
偷懒写法
tsx
// Server Component
const product = await db.product.findFirst();
// 假设 product 对象里有一大堆不需要展示的字段,甚至包含巨大的 description 文本
// 这一大坨数据都会被打包进 HTML 里发给浏览器
return <ProductInfo data={product} />;** 只有用到的才是必要的**
tsx
// Server Component
const product = await db.product.findFirst({
select: { id: true, name: true, price: true, image: true }
});
// 现在的 payload 只有几百字节
return <ProductInfo data={product} />;记住:RSC 的 props 传输是有成本的
四、 滥用 useEffect:
“React 性能差,90% 是因为不必要的重渲染。” 而不必要的重渲染,一半以上来自 useEffect。
典型的“多此一举” 很多时候,我们把 useEffect 当成了 Vue 的 watch 用。
tsx
function SearchList({ data, query }) {
const [filtered, setFiltered] = useState([]);
// 流程:渲染 -> 触发 Effect -> 更新 State -> 再次渲染
useEffect(() => {
setFiltered(data.filter(item => item.includes(query)));
}, [data, query]);
return <div>{filtered.map(...)}</div>;
}计算属性才是正解 在渲染过程中直接计算。如果计算量大,套个 useMemo;如果不大,直接算就行了。
tsx
function SearchList({ data, query }) {
// 流程:渲染时直接得出结果,0 副作用
const filtered = useMemo(() => {
return data.filter(item => item.includes(query));
}, [data, query]);
return <div>{filtered.map(...)}</div>;
}另外,像滚动监听这种高频事件,能不走 React State 就别走。用 useRef 直接操作 DOM,或者用 CSS 变量,性能体验完全是两个维度的。
写在最后
Vercel 这份清单其实没有太多“黑科技”,更多的是对开发习惯的纠正。
性能优化往往不是靠最后加一个复杂的缓存层,而是体现在每一行代码的决策里:是串行还是并行?是全量引入还是按需加载?是派生状态还是副作用?
把这些细节当成肌肉记忆,你的 React 应用自然会快起来。
(P.S. 有兴趣看原版 Prompt 的可以去搜 vercel-labs/agent-skills,里面还有关于 TypeScript 和 Vercel 部署的各种干货)