React Performance

提供React与Next.js的性能优化模式,涵盖数据获取、打包大小、组件渲染等关键领域。

已扫描
适合谁
前端开发工程师、全栈开发者
不适合谁
非前端技术背景人员、无React/Next.js项目经验者
国内可用性
需网络配置。可能需要网络配置或第三方服务可访问。
安装难度
新手友好(★☆☆)。基于终端操作、依赖、API Key 和本地环境要求的初步判断。

安装与下载

openclaw skills install @wpank/react-performance

Skill 说明

命令、参数、文件名以原文为准

React 性能优化模式

React 与 Next.js 应用的性能优化指南。涵盖 7 个优先级分类的优化模式,按影响程度排序。详细示例见 references/

适用场景

  • 编写新的 React 组件或 Next.js 页面
  • 实现数据获取(客户端或服务端)
  • 审查或重构代码以提升性能
  • 优化打包体积或加载时间

优先级分类

#分类影响程度
1异步 / 水瀑布严重
2打包体积严重
3服务端组件
4重新渲染
5渲染
6客户端数据
7JavaScript 性能低-中

安装方法

OpenClaw / Moltbot / Clawbot

npx clawhub@latest install react-performance

1. 异步 — 消除水瀑布(严重)

并行化独立操作

顺序的 await 是 React 应用中最严重的性能问题。

// 错误 — 顺序执行,需 3 次网络请求
const user = await fetchUser()
const posts = await fetchPosts()
const comments = await fetchComments()

// 正确 — 并行执行,仅需 1 次网络请求
const [user, posts, comments] = await Promise.all([
  fetchUser(), fetchPosts(), fetchComments(),
])

延迟 await 直到真正需要时

await 移入实际使用值的分支中。

// 错误 — 两个分支都阻塞了等待
async function handle(userId: string, skip: boolean) {
  const data = await fetchUserData(userId)
  if (skip) return { skipped: true }    // 仍需等待
  return process(data)
}

// 正确 — 仅在需要时才阻塞
async function handle(userId: string, skip: boolean) {
  if (skip) return { skipped: true }    // 立即返回
  return process(await fetchUserData(userId))
}

合理使用 Suspense 边界

让布局立即显示,而依赖数据的部分独立加载。

// 错误 — 整个页面被阻塞
async function Page() {
  const data = await fetchData()
  return <div><Sidebar /><Header /><DataDisplay data={data} /><Footer /></div>
}

// 正确 — 布局立即渲染,数据流式加载
function Page() {
  return (
    <div>
      <Sidebar /><Header />
      <Suspense fallback={<Skeleton />}><DataDisplay /></Suspense>
      <Footer />
    </div>
  )
}
async function DataDisplay() {
  const data = await fetchData()
  return <div>{data.content}</div>
}

使用 use() 在组件间共享一个 Promise,避免重复请求。


2. 打包体积(严重)

避免使用 barrel 文件导入

barrel 文件会加载数千个未使用的模块。直接导入可节省 200–800ms。

// 错误 — 加载 1,583 个模块
import { Check, X, Menu } from 'lucide-react'

// 正确 — 仅加载 3 个模块
import Check from 'lucide-react/dist/esm/icons/check'
import X from 'lucide-react/dist/esm/icons/x'
import Menu from 'lucide-react/dist/esm/icons/menu'

Next.js 13.5+:在配置中启用 experimental.optimizePackageImports

常见受影响库:lucide-react@mui/materialreact-icons@radix-uilodashdate-fns

对重型组件使用动态导入

import dynamic from 'next/dynamic'
const MonacoEditor = dynamic(
  () => import('./monaco-editor').then((m) => m.MonacoEditor),
  { ssr: false }
)

延迟加载非关键第三方库

分析工具、日志记录、错误追踪等,在客户端 hydration 后再加载,使用 dynamic(){ ssr: false }

根据用户意图预加载

const preload = () => { void import('./monaco-editor') }
<button onMouseEnter={preload} onFocus={preload} onClick={onClick}>打开编辑器</button>

3. 服务端组件(高)

减少 RSC 边界上的序列化开销

仅传递客户端实际使用的字段。

// 错误 — 序列化全部 50 个用户字段
return <Profile user={user} />

// 正确 — 仅序列化 1 个字段
return <Profile name={user.name} />

使用组合实现并行数据获取

RSC 在组件树内按顺序执行。通过结构调整实现并行。

// 错误 — 侧边栏需等待头部数据获取完成
export default async function Page() {
  const header = await fetchHeader()
  return <div><div>{header}</div><Sidebar /></div>
}

// 正确 — 兄弟异步组件同时发起请求
async function Header() { return <div>{await fetchHeader()}</div> }
async function Sidebar() { return <nav>{(await fetchSidebarItems()).map(renderItem)}</nav> }
export default function Page() { return <div><Header /><Sidebar /></div> }

使用 React.cache() 实现每请求去重

import { cache } from 'react'
export const getCurrentUser = cache(async () => {
  const session = await auth()
  if (!session?.user?.id) return null
  return await db.user.findUnique({ where: { id: session.user.id } })
})

使用基础类型参数(而非内联对象)—— React.cache() 使用 Object.is 判断缓存键。Next.js 会自动对 fetch 去重,但数据库查询、认证检查和计算逻辑仍需手动使用 React.cache()

使用 after() 处理非阻塞操作

import { after } from 'next/server'
export async function POST(request: Request) {
  await updateDatabase(request)
  after(async () => { logUserAction({ userAgent: request.headers.get('user-agent') }) })
  return Response.json({ status: 'success' })
}

4. 重新渲染优化(中)

在渲染期间推导状态 — 而非在副作用中

// 错误 — 冗余状态 + 副作用
const [fullName, setFullName] = useState('')
useEffect(() => { setFullName(first + ' ' + last) }, [first, last])

// 正确 — 直接在渲染中推导
const fullName = first + ' ' + last

使用函数式 setState 保持回调稳定

// 错误 — 每次 items 变化都会重新创建
const addItem = useCallback((item: Item) => {
  setItems([...items, item])
}, [items])

// 正确 — 稳定,始终获取最新状态
const addItem = useCallback((item: Item) => {
  setItems((curr) => [...curr, item])
}, [])

延迟状态读取至使用点

如果仅在回调中读取动态状态,请避免订阅。

// 错误 — 每次 searchParams 变化都会重新渲染
const searchParams = useSearchParams()
const handleShare = () => shareChat(chatId, { ref: searchParams.get('ref') })

// 正确 — 按需读取
const handleShare = () => {
  const ref = new URLSearchParams(window.location.search).get('ref')
  shareChat(chatId, { ref })
}

延迟状态初始化

// 错误 — 每次渲染都执行 JSON.parse
const [settings] = useState(JSON.parse(localStorage.getItem('s') || '{}'))

// 正确 — 仅执行一次
const [settings] = useState(() => JSON.parse(localStorage.getItem('s') || '{}'))

订阅派生的布尔值

// 错误 — 每像素变化都会重新渲染
const width = useWindowWidth(); const isMobile = width < 768

// 正确 — 仅在布尔值翻转时重新渲染
const isMobile = useMediaQuery('(max-width: 767px)')

非紧急更新使用过渡

// 错误 — 滚动时阻塞 UI
const handler = () => setScrollY(window.scrollY)

// 正确 — 非阻塞
const handler = () => startTransition(() => setScrollY(window.scrollY))

将耗时操作提取到记忆化组件中

const UserAvatar = memo(function UserAvatar({ user }: { user: User }) {
  const id = useMemo(() => computeAvatarId(user), [user])
  return <Avatar id={id} />
})
function Profile({ user, loading }: Props) {
  if (loading) return <Skeleton />
  return <div><UserAvatar user={user} /></div>
}

注:React Compiler 会自动处理手动 memo()/useMemo() 的需求,无需额外添加。


5. 渲染性能(中等)

长列表使用 CSS content-visibility

对于 1000 项的列表,浏览器可跳过约 990 个不可见元素(初始渲染快 10 倍)。

.list-item { content-visibility: auto; contain-intrinsic-size: 0 80px; }

将静态 JSX 提升至组件外部

避免重复创建,尤其适用于大型 SVG 节点。React Compiler 会自动完成此优化。

const skeleton = <div className="skeleton" />
function Container() { return <div>{loading && skeleton}</div> }

6. 客户端数据(中等)

使用 SWR 实现请求去重与缓存

// 错误 — 每个实例独立发起请求
useEffect(() => { fetch('/api/users').then(r => r.json()).then(setUsers) }, [])

// 正确 — 多个实例共享同一请求
const { data: users } = useSWR('/api/users', fetcher)

7. JS 性能(低 - 中等)

使用 Set/Map 实现 O(1) 查找

// 错误 — 时间复杂度 O(n)
items.filter(i => allowed.includes(i.id))
// 正确 — 时间复杂度 O(1)
const allowedSet = new Set(allowed)
items.filter(i => allowedSet.has(i.id))

合并数组遍历

// 错误 — 执行 3 次遍历
const a = users.filter(u => u.isAdmin)
const t = users.filter(u => u.isTester)
// 正确 — 仅执行 1 次遍历
const a: User[] = [], t: User[] = []
for (const u of users) { if (u.isAdmin) a.push(u); if (u.isTester) t.push(u) }

此外: 使用提前返回、在循环中缓存属性访问、将正则表达式移出循环、在热点路径中优先使用 for...of


快速决策指南

  1. 页面加载慢? → 优先处理包体积(2),其次异步瀑布流(1)
  2. 交互卡顿? → 优先处理重新渲染问题(4),其次 JS 性能(7)
  3. 服务端页面慢? → RSC 序列化与并行获取(3)
  4. 客户端数据过期或缓慢? → 使用 SWR(6)
  5. 长列表卡顿? → 使用 content-visibility(5)
W
@wpank

已收录 3 个 Skill

相关推荐