加上这个React最佳实践Skill,再也不怕AI写代码不考虑性能优化了
Vercel 团队维护的 React 和 Next.js 性能优化指南的Skill: React Best Practices,包含 45 条规则,8大类的优化的方向:
| 类别 | 说明 |
|---|---|
| 消除瀑布流请求 | 瀑布流是性能杀手,每个顺序 await 都会增加完整的网络延迟 |
| Bundle 体积优化 | 减少初始 bundle 大小,改善首次可交互时间(TTI)和最大内容绘制(LCP) |
| 服务端性能 | 优化服务端渲染和数据获取,消除服务端瀑布流,减少响应时间 |
| 客户端数据获取 | 自动去重和高效的数据获取模式,减少冗余网络请求 |
| 重渲染优化 | 减少不必要的重渲染,最小化浪费的计算,提升 UI 响应性 |
| 渲染性能 | 优化渲染过程,减少浏览器的工作量 |
| JavaScript 性能 | 热路径的微优化,积少成多也能带来明显改善 |
| 高级模式 | 针对特定场景的高级模式,需要谨慎实现 |
接着我们来具体看看第一条:消除瀑布流请求这个类别,它一共分为了5条rules:
1、async-defer-await:在真正需要的时候再使用await
核心思想:在真正需要使用到 await 时使用,避免阻塞前置不需要 await 的地方。
错误示例(两个分支都被阻塞):
async function handleRequest(userId: string, skipProcessing: boolean) {
const userData = await fetchUserData(userId) // 总是等待
if (skipProcessing) {
// 虽然立即返回,但已经等待了 userData
return { skipped: true }
}
// 只有这个分支使用 userData
return processUserData(userData)
}
正确示例(只在需要时阻塞):
async function handleRequest(userId: string, skipProcessing: boolean) {
if (skipProcessing) {
// 立即返回,无需等待
return { skipped: true }
}
// 只在需要时获取
const userData = await fetchUserData(userId)
return processUserData(userData)
}
另一个实际场景:
// 错误:每次都获取权限
async function updateResource(resourceId: string, userId: string) {
const permissions = await fetchPermissions(userId)
const resource = await getResource(resourceId)
if (!resource) {
return { error: 'Not found' }
}
if (!permissions.canEdit) {
return { error: 'Forbidden' }
}
return await updateResourceData(resource, permissions)
}
// 正确:只在需要时获取
async function updateResource(resourceId: string, userId: string) {
const resource = await getResource(resourceId)
if (!resource) {
return { error: 'Not found' } // 提前返回,省掉权限请求
}
const permissions = await fetchPermissions(userId)
if (!permissions.canEdit) {
return { error: 'Forbidden' }
}
return await updateResourceData(resource, permissions)
}
2、async-parallel:使用 Promise.all() 并发执行
核心思想:当每个异步操作之间没有依赖关系时,可以使用 Promise.all() 来进行并发执行
错误示例(串行执行,3 次网络往返):
const user = await fetchUser()
const posts = await fetchPosts()
const comments = await fetchComments()
正确示例(并行执行,1 次网络往返):
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments()
])
这可能是最简单但也是最有效的优化之一。
3、async-dependencies:使用 better-all 处理部分依赖
核心思想:当异步操作之间存在依赖时,可以使用 better-all 库来自动最大化并行度。
问题场景:profile 依赖 user,但 config 不依赖其他操作。
错误示例(profile 不必要地等待 config):
const [user, config] = await Promise.all([
fetchUser(),
fetchConfig()
])
const profile = await fetchProfile(user.id) // config 已完成,但 profile 还要等
正确示例(config 和 profile 并行运行):
import { all } from 'better-all'
const { user, config, profile } = await all({
async user() { return fetchUser() },
async config() { return fetchConfig() },
async profile() {
// 只等待 user,不等待 config
return fetchProfile((await this.$.user).id)
}
})
better-all 会自动分析依赖关系,在最早可能的时刻启动每个任务。
4、async-api-routes:API 路由中尽早开始 Promise
核心思想:在 API 路由和 Server Actions 中,立即启动独立操作,即使你还不需要 await 它们。
错误示例(config 等待 auth,data 等待两者):
export async function GET(request: Request) {
const session = await auth()
const config = await fetchConfig() // 等 auth 完成才开始
const data = await fetchData(session.user.id)
return Response.json({ data, config })
}
正确示例(auth 和 config 立即并行启动):
export async function GET(request: Request) {
// 立即启动,不等待
const sessionPromise = auth()
const configPromise = fetchConfig()
// 需要 session 时才 await
const session = await sessionPromise
// config 和 data 并行获取
const [config, data] = await Promise.all([
configPromise,
fetchData(session.user.id)
])
return Response.json({ data, config })
}
关键技巧:先启动 Promise,后 await。
5、async-suspense-boundaries:使用 Suspense
核心思想:不要在 async 组件中 await 数据后再返回 JSX,而是使用 Suspense 边界让外层 UI 先显示。
错误示例(整个页面被数据获取阻塞):
async function Page() {
const data = await fetchData() // 阻塞整个页面
return (
<div>
<div>Sidebar</div>
<div>Header</div>
<div>
<DataDisplay data={data} />
</div>
<div>Footer</div>
</div>
)
}
整个布局都要等数据,尽管只有中间部分需要它。
正确示例(外层立即显示,数据流式加载):
function Page() {
return (
<div>
<div>Sidebar</div>
<div>Header</div>
<div>
<Suspense fallback={<Skeleton />}>
<DataDisplay />
</Suspense>
</div>
<div>Footer</div>
</div>
)
}
async function DataDisplay() {
const data = await fetchData() // 只阻塞这个组件
return <div>{data.content}</div>
}
Sidebar、Header、Footer 立即渲染,只有 DataDisplay 等待数据。
进阶:多个组件共享 Promise
function Page() {
// 立即开始获取,但不 await
const dataPromise = fetchData()
return (
<div>
<div>Sidebar</div>
<div>Header</div>
<Suspense fallback={<Skeleton />}>
<DataDisplay dataPromise={dataPromise} />
<DataSummary dataPromise={dataPromise} />
</Suspense>
<div>Footer</div>
</div>
)
}
function DataDisplay({ dataPromise }: { dataPromise: Promise<Data> }) {
const data = use(dataPromise) // 使用 React 19 的 use() hook
return <div>{data.content}</div>
}
function DataSummary({ dataPromise }: { dataPromise: Promise<Data> }) {
const data = use(dataPromise) // 复用同一个 Promise
return <div>{data.summary}</div>
}
两个组件共享同一个 Promise,只发起一次请求。
注意:这种方式需要权衡。更快的首次绘制 vs 重绘重排
如何安装这个 Skill
直接将github的这整个文件夹:github.com/vercel-labs… 放到 ~/.claude/skills(mac电脑)下即可 。安装完成后,Claude Code会自动判断是否在需要的时候使用这个skill,当然也可以通过以下方式直接调用这个 Skill:
/vercel-react-best-practices