Communitygithub.com

React 性能优化

适用于 React 和 Next.js 的性能优化模式,改编自 Vercel 工程实践。

React 性能优化 是什么?

这个技能提供了系统化的 React 18/19 和 Next.js 性能优化模式,基于 Vercel Labs 的 `react-best-practices` 仓库改编。它按照优先级(从高到低)将所有优化规则组织为 8 个类别,涵盖消除请求瀑布流、包体积优化、服务端性能、客户端数据请求、重渲染优化、渲染性能、JavaScript 性能以及高级模式。每个类别都包含具体的代码示例(正确与错误用法对比)和决策树指导,适用于代码审查和重构。技能还提供了优先级索引表,帮助开发者根据场景快速定位最相关的优化规则。支持平台包括 React 18/19 和 Next.js(含 App Router 和 Pages Router),适用于主动审查、性能诊断和 Lighthouse Core Web Vitals 回归分析。

兼容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/react-performance

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

React 性能优化 是做什么的?

适用于 React 18/19 和 Next.js 的性能优化模式,改编自 Vercel Labs react-best-practices(MIT,v1.0.0)。此技能按优先级组织规则,并为主动代码审查和重构提供决策树指导。

何时激活

  • 编写或审查 React/Next.js 代码以优化性能
  • 诊断页面加载缓慢、交互迟滞或客户端 CPU 占用过高问题
  • 审查包体积或 Lighthouse Core Web Vitals 回归
  • 消除服务端组件 / API 路由中的请求瀑布流
  • 减少客户端重渲染
  • 优化长列表、动画或水合(hydration)
  • 审查涉及 app/、pages/、components/ 或数据层的 PR 中的优化选择

优先级索引

优先级类别前缀适用场景
1 — 致命消除瀑布流async-任何时候 await 后跟独立的 await
2 — 致命包体积优化bundle-首次加载的 JS、路由级导入、第三方库
3 — 高服务端性能server-RSC、Server Actions、API 路由、SSR
4 — 中高客户端数据请求client-SWR / TanStack Query / hooks 中的原生 fetch
5 — 中重渲染优化rerender-高频状态更新、父子组件扇出
6 — 中渲染性能rendering-长列表、动画、水合
7 — 低中JavaScript 性能js-热点循环、频繁分配
8 — 低高级模式advanced-Effect-event 集成、稳定引用

1. 消除瀑布流(致命)

“瀑布流是 #1 的性能杀手”——每个顺序的 await 都会增加完整的网络延迟。

在 await 之前检查廉价条件

在等待远程数据之前,先检查同步条件(props、环境变量、硬编码的标志)。

// 错误
async function Page({ id }: { id: string }) {
  const flag = await getFlag("show-page");
  if (!flag || !id) return null;
  const data = await getData(id);
  // ...
}

// 正确 — 先通过廉价同步条件短路
async function Page({ id }: { id: string }) {
  if (!id) return null;
  const flag = await getFlag("show-page");
  if (!flag) return null;
  const data = await getData(id);
}

延迟 await 直到使用

将 await 移到使用它的分支中。

// 错误 — 在决定是否需要数据之前就 await
const user = await getUser(id);
if (mode === "guest") return renderGuest();
return renderUser(user);

// 正确
if (mode === "guest") return renderGuest();
const user = await getUser(id);
return renderUser(user);

Promise.all 用于独立工作

// 错误 — 顺序执行
const user = await getUser(id);
const posts = await getPosts(id);
const followers = await getFollowers(id);

// 正确 — 并行执行
const [user, posts, followers] = await Promise.all([
  getUser(id),
  getPosts(id),
  getFollowers(id),
]);

部分依赖 — 提前开始,延迟 await

// 正确 — 启动所有 promise,只在需要结果时才 await
const userP = getUser(id);
const postsP = getPosts(id);
const profile = await getProfile(id);
if (profile.private) return null;
const [user, posts] = await Promise.all([userP, postsP]);

使用 Suspense 进行流式传输

将 <Suspense> 边界推近数据,以便页面可以在较慢的子树流式传输时渲染其能渲染的内容。权衡:内容到达时可能发生布局偏移——预留空间(骨架屏或 min-height)。

服务端组件:通过组合实现并行

// 错误 — 一个组件内的兄弟 await 顺序执行
export default async function Page() {
  const user = await getUser();
  const cart = await getCart();
  return <View user={user} cart={cart} />;
}

// 正确 — 拆分为子组件,React 并行运行它们
export default async function Page() {
  return (
    <View>
      <UserSection />
      <CartSection />
    </View>
  );
}

2. 包体积优化(致命)

直接导入,而非 barrel 文件

Barrel index.ts 文件迫使打包器遍历整个模块图,即使 tree-shaking 移除了大部分内容。在许多真实应用中,直接导入可以节省 200-800ms 的首次加载 JS。

// 错误
import { Button, Card, Modal } from "@/components";

// 正确
import { Button } from "@/components/Button";
import { Card } from "@/components/Card";
import { Modal } from "@/components/Modal";

Next.js 13.5+ 具有 Optimize Package Imports 功能,可自动为列出的包执行此操作——请使用它;对于未列出的库,仍需要手动直接导入。

静态可分析路径

// 错误 — 破坏打包器/跟踪分析
const mod = await import(`./pages/${name}`);

// 正确 — 每个分支显式定义
const mod = name === "home" ? await import("./pages/home") : await import("./pages/about");

对重组件使用动态导入

import dynamic from "next/dynamic";

const HeavyChart = dynamic(() => import("./HeavyChart"), {
  loading: () => <Skeleton />,
  ssr: false, // 当仅客户端时
});

延迟第三方脚本

在水合之后加载分析、日志记录、支持组件。使用 next/script 并设置 strategy="afterInteractive"(默认)或 "lazyOnload"。

条件模块加载

if (user.role === "admin") {
  const { AdminPanel } = await import("./admin/AdminPanel");
  // ...
}

悬停/聚焦时预加载

在悬停时触发 <link rel="preload"> 或 import(),以便在用户点击时包已在缓存中。

3. 服务端性能(高)

像 API 路由一样验证 Server Actions

每个 "use server" 函数都是一个公共端点。在 action 内部进行身份验证和授权——永远不要依赖调用它的客户端组件的门控。

"use server";
export async function deleteUser(formData: FormData) {
  const session = await getSession();
  if (!session?.user) throw new Error("Unauthorized");
  const targetId = String(formData.get("id"));
  if (session.user.role !== "admin" && session.user.id !== targetId) {
    throw new Error("Forbidden");
  }
  await db.user.delete({ where: { id: targetId } });
}

React.cache() 用于每次请求的去重

import { cache } from "react";

export const getUser = cache(async (id: string) => {
  return db.user.findUnique({ where: { id } });
});

React.cache 在单个请求内去重。从三个服务端组件调用 getUser("1") 在同一渲染中=一次数据库查询。

跨请求数据的 LRU 缓存

对于不随请求变化的数据(配置、查找表),使用 LRU 缓存或 unstable_cache 在 React 外部缓存。

避免 RSC props 中的重复序列化

当一个服务端组件将相同的数据渲染到多个客户端组件时,数据会为每个消费者序列化一次。将客户端组件向上提升并传递 children。

将静态 I/O 提升到模块作用域

// 正确 — 在模块加载时运行一次
const fontData = readFileSync(fontPath);

export async function Page() {
  return <Banner font={fontData} />;
}

RSC/SSR 中禁止可变的模块级状态

服务器上的模块状态在所有请求之间共享——用户之间存在竞态条件。使用请求作用域的存储(headers()、cookies()、异步上下文)代替。

最小化传递给客户端组件的数据

只序列化客户端所需的内容。在数据库层剥离字段、分页、投影列。

使用 Promise.all 并行化每个项目的嵌套获取

const users = await getUsers();
const enriched = await Promise.all(
  users.map(async (u) => ({ ...u, posts: await getPostsFor(u.id) })),
);

使用 after() 进行非阻塞工作

Next.js 15 的 after() 在响应发送后运行工作——日志记录、缓存预热、分析。

import { after } from "next/server";
export async function GET() {
  const data = await getData();
  after(() => logAnalytics(data));
  return Response.json(data);
}

4. 客户端数据请求(中高)

SWR / TanStack Query 用于去重

多个组件调用 useUser(id) 应共享一个网络请求和一个缓存条目。使用 SWR 或 TanStack Query——绝不应对共享数据使用自己编写的 useEffect + fetch。

去重全局事件监听器

// 错误 — 每个组件添加自己的
useEffect(() => {
  window.addEventListener("scroll", handler);
  return () => window.removeEventListener("scroll", handler);
}, []);

// 正确 — 通过钩子 + 全局主题实现单一共享监听器
const useScroll = createScrollHook(); // 底层为单例主题

滚动的被动监听器

window.addEventListener("scroll", handler, { passive: true });

提升滚动流畅度;监听器不能调用 preventDefault()。

localStorage:版本化 + 最小化

  • 始终存储 version 字段;模式更改时递增版本号,并迁移或丢弃旧数据
  • 保持负载小巧——localStorage 是同步的,会阻塞主线程

5. 重渲染优化(中)

不要订阅仅在回调中使用的状态

// 错误 — 每次 count 发生变化时都会重渲染
const count = useStore((s) => s.count);
const handler = () => doSomething(count);

// 正确 — 在调用时读取一次
const handler = () => {
  const count = useStore.getState().count;
  doSomething(count);
};

将昂贵的工作提取到记忆化组件中

// 正确 — 子组件仅当 `items` 变化时才重渲染
const Heavy = memo(function Heavy({ items }: { items: Item[] }) {
  return <Chart data={transform(items)} />;
});

提升默认的非原始 props

// 错误 — 每次渲染创建新数组,破坏 memo
<List items={items ?? []} />

// 正确
const EMPTY: Item[] = [];
<List items={items ?? EMPTY} />

effect 中的原始依赖项

// 错误 — 每次渲染创建新对象引用
useEffect(() => {}, [{ id, name }]);

// 正确 — 原始值
useEffect(() => {}, [id, name]);

订阅派生布尔值,而非原始值

// 错误 — 任何购物车更改都会重渲染
const cart = useStore((s) => s.cart);
const hasItems = cart.length > 0;

// 正确 — 仅当空/非空状态翻转时才重渲染
const hasItems = useStore((s) => s.cart.length > 0);

在渲染期间派生,绝不通过 useEffect

// 错误
const [full, setFull] = useState("");
useEffect(() => setFull(`${first} ${last}`), [first, last]);

// 正确
const full = `${first} ${last}`;

使用函数式 setState 获取稳定的回调

// 正确
const increment = useCallback(() => setCount((c) => c + 1), []);

对昂贵值使用惰性状态初始化器

const [tree] = useState(() => parseTree(largeInput));

避免对简单原始值使用 memo

useMemo(() => x + 1, [x]) 是开销。Memo 在对象引用和昂贵计算上才值得使用。

拆分具有独立依赖项的钩子

// 错误 — 如果任一源发生变化,两个选择器都会重新运行
const { a, b } = useSomething(source1, source2);

// 正确
const a = useA(source1);
const b = useB(source2);

将交互逻辑移到事件处理程序中

事件处理程序仅在用户操作时运行——useEffect 在依赖项变化时重新运行。

对非紧急更新使用 startTransition

const [pending, startTransition] = useTransition();
startTransition(() => setFilters(newFilters));

对昂贵渲染使用 useDeferredValue

const deferredQuery = useDeferredValue(query);
const results = useMemo(() => expensiveSearch(deferredQuery), [deferredQuery]);

对瞬态频繁值使用 useRef

对于变化频繁但不应触发重渲染的值(时间戳、上次按键、累加器)。

不要在组件内部定义组件

// 错误 — 每次 Outer 渲染都会创建新的 Inner 组件
function Outer() {
  const Inner = () => <span />;
  return <Inner />;
}

每次渲染都会创建一个新的 Inner 类型,这会破坏协调并卸载子组件。

6. 渲染性能(中)

动画化容器而非 SVG

围绕 SVG 转换 <div> 容器是 GPU 加速的;而转换 SVG 本身会触发绘制。

对长列表使用 content-visibility: auto

.row { content-visibility: auto; contain-intrinsic-size: auto 80px; }

浏览器跳过屏幕外渲染——对于包含数百行的列表,这是一个重大的优化。

提升静态 JSX

const STATIC_HEADER = <h1>Title</h1>;
function Page() {
  return <>{STATIC_HEADER}<Body /></>;
}

SVG:降低坐标精度

d="M10.123456,20.654321" → d="M10.12,20.65"。每个数字都会消耗字节;视觉差异在亚像素级别。

通过内联脚本实现无闪烁水合

对于水合前所需的值(主题、语言环境),内联一个 <script>,在 React 挂载之前设置 document.documentElement.dataset.*。

窄范围内抑制预期的水合不匹配

<time suppressHydrationWarning>{new Date().toLocaleString()}</time>

仅对已知会分歧的叶子节点使用——切勿用于包含其他子节点的树。

使用 <Activity> 进行显示/隐藏而非挂载/卸载

React 19 的 <Activity mode="visible|hidden"> 保持树状态和 effects 挂载但隐藏——对于标签页和折叠面板来说,比卸载/重新挂载更廉价。

条件渲染使用三元表达式而非 &&

// 错误 — `0` 会渲染为文本节点
{count && <Badge>{count}</Badge>}

// 正确
{count > 0 ? <Badge>{count}</Badge> : null}

useTransition 用于加载状态

将 startTransition 与 action 配对;React 在下一个状态计算期间,会显示先前的 UI 并同时设置 isPending。

React DOM 资源提示

import { preload, preconnect } from "react-dom";
preload("/api/critical", { as: "fetch" });
preconnect("https://api.example.com")

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

相关技能