Communitygithub.com

React 性能优化

适用于 React 18/19 和 Next.js 的性能优化模式,源自 Vercel 工程最佳实践。

React 性能优化 是什么?

该技能源自 Vercel Labs 的开源项目 `react-best-practices`,提供了一套完整的 React 和 Next.js 性能优化模式。它按照优先级 (CRITICAL、HIGH、MEDIUM、LOW) 组织规则,并附有决策树指导,帮助开发者在代码审查和重构时主动发现并解决性能问题。支持 React 18/19 和 Next.js (App Router 和 Pages Router)。核心优化点包括消除串行请求 (Waterfalls)、优化打包体积、提升服务端渲染性能、减少客户端重渲染以及优化长列表和动画。每个规则都提供了具体的代码示例,展示错误的写法和正确的优化方式,覆盖了从基础组件到高级 Server Components 的多种场景。

兼容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/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 回退
  • 消除 Server Components / API 路由中的串行请求
  • 减少客户端重渲染
  • 优化长列表、动画或水合
  • 审查涉及 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-事件集成、稳定 ref

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)。

Server Components:通过组合实现并行

// 不正确——一个组件内的兄弟 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 在单个请求内去重。从三个 Server Components 中调用 getUser("1") 在同一渲染中 = 一次数据库查询。

跨请求数据的 LRU 缓存

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

避免 RSC props 中的重复序列化

当 Server Component 将相同的数据渲染到多个 Client Components 时,数据会为每个消费者序列化一次。将 Client Component 提升并向其传递 children。

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

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

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

RSC/SSR 中不使用可变的模块级状态

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

最小化传递给 Client Components 的数据

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

使用每项的 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);
}, []);

// 正确——通过 hook + 全局 subject 使用单个共享监听器
const useScroll = createScrollHook(); // 底层是单例 subject

滚动使用被动监听器

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);
};

将昂贵工作提取到 memoized 组件中

// 正确——子组件仅在 `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} />

effects 中使用原始值依赖

// 不正确——每次渲染创建新对象引用
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 在对象引用和昂贵计算上才值得。

拆分具有独立依赖的 hooks

// 不正确——如果任一源变化,两个选择器都会重新运行
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 用于临时高频值

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

不要在组件内部定义组件

// 不正确——Inner 在每次 Outer 渲染时都是一个新组件
function Outer() {
  const Inner = () => <span />;
  return <Inner />;
}

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

6. 渲染性能 (中)

动画化 wrapper,而非 SVG

转换 SVG 周围的 <div> wrapper 是 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 在下一个状态计算期间显示带有 isPending 的先前 UI。

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/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

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.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

相关技能