Skip to content

扒了一下 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 部署的各种干货)

Last updated: