react-performance

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

React Performance

React 性能优化

Performance optimization patterns for React 18/19 and Next.js, adapted from Vercel Labs
react-best-practices
(MIT, v1.0.0). This skill organizes rules by priority and provides decision-tree guidance for active code review and refactoring.
适用于React 18/19和Next.js的性能优化模式,改编自Vercel Labs的
react-best-practices
(MIT许可证,v1.0.0版本)。本技能按优先级整理规则,并为代码评审和重构提供决策树指导。

When to Activate

适用场景

  • Writing or reviewing React/Next.js code for performance
  • Diagnosing slow page loads, slow interactions, or high CPU on the client
  • Auditing bundle size or Lighthouse Core Web Vitals regressions
  • Removing waterfalls in Server Components / API routes
  • Reducing client-side re-renders
  • Optimizing long lists, animations, or hydration
  • Auditing optimization choices in PRs touching
    app/
    ,
    pages/
    ,
    components/
    , or data layers
  • 编写或评审React/Next.js代码以优化性能时
  • 诊断页面加载缓慢、交互卡顿或客户端CPU占用过高问题时
  • 审计包体积或Lighthouse核心Web指标回归问题时
  • 消除Server Components/API路由中的请求瀑布时
  • 减少客户端重渲染次数时
  • 优化长列表、动画或hydration(水合)过程时
  • 审计涉及
    app/
    pages/
    components/
    或数据层的PR中的优化方案时

Priority Index

优先级索引

PriorityCategoryPrefixWhen it matters
1 — CRITICALEliminating Waterfalls
async-
Anytime
await
is followed by independent
await
2 — CRITICALBundle Size Optimization
bundle-
First-load JS, route-level imports, third-party libs
3 — HIGHServer-Side Performance
server-
RSC, Server Actions, API routes, SSR
4 — MEDIUM-HIGHClient-Side Data Fetching
client-
SWR / TanStack Query / raw
fetch
in hooks
5 — MEDIUMRe-render Optimization
rerender-
High-frequency state updates, parent-child fan-out
6 — MEDIUMRendering Performance
rendering-
Long lists, animations, hydration
7 — LOW-MEDIUMJavaScript Performance
js-
Hot loops, frequent allocations
8 — LOWAdvanced Patterns
advanced-
Effect-event integration, stable refs
优先级类别前缀适用场景
1 — 关键消除请求瀑布
async-
任何
await
后跟随独立
await
的场景
2 — 关键包体积优化
bundle-
首屏加载JS、路由级导入、第三方库相关场景
3 — 高服务端性能
server-
RSC、Server Actions、API路由、SSR相关场景
4 — 中高客户端数据获取
client-
Hooks中使用SWR/TanStack Query/原生
fetch
的场景
5 — 中重渲染优化
rerender-
高频状态更新、父子组件扇出场景
6 — 中渲染性能
rendering-
长列表、动画、hydration相关场景
7 — 中低JavaScript性能
js-
热循环、频繁内存分配场景
8 — 低高级模式
advanced-
Effect与事件集成、稳定refs相关场景

1. Eliminating Waterfalls (CRITICAL)

1. 消除请求瀑布(CRITICAL)

"Waterfalls are the #1 performance killer" — every sequential
await
adds full network latency.
"请求瀑布是性能的头号杀手" — 每个顺序执行的
await
都会增加完整的网络延迟。

Cheap conditions before await

在await前先判断低成本同步条件

Check sync conditions (props, env, hardcoded flags) before awaiting remote data.
ts
// INCORRECT
async function Page({ id }: { id: string }) {
  const flag = await getFlag("show-page");
  if (!flag || !id) return null;
  const data = await getData(id);
  // ...
}

// CORRECT — short-circuit on cheap sync condition first
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);
}
在等待远程数据前,先检查同步条件(props、环境变量、硬编码标志)。
ts
// INCORRECT
async function Page({ id }: { id: string }) {
  const flag = await getFlag("show-page");
  if (!flag || !id) return null;
  const data = await getData(id);
  // ...
}

// CORRECT — 先通过低成本同步条件提前终止
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);
}

Defer awaits until used

延迟await直到需要使用数据时

Move
await
into the branch that uses it.
ts
// INCORRECT — awaits before deciding it needs the data
const user = await getUser(id);
if (mode === "guest") return renderGuest();
return renderUser(user);

// CORRECT
if (mode === "guest") return renderGuest();
const user = await getUser(id);
return renderUser(user);
await
移到需要使用数据的分支中。
ts
// INCORRECT — 在决定是否需要数据前就执行了await
const user = await getUser(id);
if (mode === "guest") return renderGuest();
return renderUser(user);

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

Promise.all for independent work

使用Promise.all并行处理独立任务

ts
// INCORRECT — sequential
const user = await getUser(id);
const posts = await getPosts(id);
const followers = await getFollowers(id);

// CORRECT — parallel
const [user, posts, followers] = await Promise.all([
  getUser(id),
  getPosts(id),
  getFollowers(id),
]);
ts
// INCORRECT — 顺序执行
const user = await getUser(id);
const posts = await getPosts(id);
const followers = await getFollowers(id);

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

Partial dependencies — start early, await late

部分依赖项——提前启动,延迟await

ts
// CORRECT — kick off all promises, await only when each result is needed
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]);
ts
// CORRECT — 先启动所有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 for streaming

使用Suspense实现流式加载

Push
<Suspense>
boundaries close to the data so the page paints what it can while slower sub-trees stream in. The trade-off: layout shift when content arrives — reserve space (skeleton or
min-height
).
<Suspense>
边界靠近数据获取逻辑,这样页面可以先渲染已有内容,较慢的子树在流式加载过程中逐步呈现。权衡点:内容加载时会出现布局偏移——需预留空间(骨架屏或
min-height
)。

Server Components: parallel through composition

Server Components:通过组合实现并行

tsx
// INCORRECT — sibling awaits run sequentially inside one component
export default async function Page() {
  const user = await getUser();
  const cart = await getCart();
  return <View user={user} cart={cart} />;
}

// CORRECT — split into children, React runs them in parallel
export default async function Page() {
  return (
    <View>
      <UserSection />
      <CartSection />
    </View>
  );
}
tsx
// INCORRECT — 单个组件内的兄弟await会顺序执行
export default async function Page() {
  const user = await getUser();
  const cart = await getCart();
  return <View user={user} cart={cart} />;
}

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

2. Bundle Size Optimization (CRITICAL)

2. 包体积优化(CRITICAL)

Direct imports, not barrels

直接导入,而非桶式导入

Barrel
index.ts
files force the bundler to walk the entire module graph even when tree-shaking removes most of it. Direct imports save 200-800ms of first-load JS in many real-world apps.
ts
// INCORRECT
import { Button, Card, Modal } from "@/components";

// CORRECT
import { Button } from "@/components/Button";
import { Card } from "@/components/Card";
import { Modal } from "@/components/Modal";
Next.js 13.5+ has Optimize Package Imports that automates this for listed packages — use it; manual direct imports still required for non-listed libs.
桶式
index.ts
文件会迫使打包工具遍历整个模块图,即使tree-shaking移除了大部分内容。直接导入在许多实际应用中可节省200-800ms的首屏加载JS时间。
ts
// INCORRECT
import { Button, Card, Modal } from "@/components";

// CORRECT
import { Button } from "@/components/Button";
import { Card } from "@/components/Card";
import { Modal } from "@/components/Modal";
Next.js 13.5+提供了Optimize Package Imports功能,可自动为指定包处理此优化——建议启用;未列出的库仍需手动直接导入。

Statically analyzable paths

使用可静态分析的路径

ts
// INCORRECT — defeats bundler/trace analysis
const mod = await import(`./pages/${name}`);

// CORRECT — explicit per branch
const mod = name === "home" ? await import("./pages/home") : await import("./pages/about");
ts
// INCORRECT — 破坏打包工具的追踪分析
const mod = await import(`./pages/${name}`);

// CORRECT — 分支中明确指定路径
const mod = name === "home" ? await import("./pages/home") : await import("./pages/about");

Dynamic imports for heavy components

动态导入重型组件

tsx
import dynamic from "next/dynamic";

const HeavyChart = dynamic(() => import("./HeavyChart"), {
  loading: () => <Skeleton />,
  ssr: false, // when client-only
});
tsx
import dynamic from "next/dynamic";

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

Defer third-party scripts

延迟加载第三方脚本

Load analytics, logging, support widgets AFTER hydration. Use
next/script
with
strategy="afterInteractive"
(default) or
"lazyOnload"
.
在hydration完成后加载分析、日志、支持小部件等脚本。使用
next/script
并设置
strategy="afterInteractive"
(默认值)或
"lazyOnload"

Conditional module loading

条件式模块加载

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

Preload on hover/focus

在悬停/聚焦时预加载

Trigger
<link rel="preload">
or
import()
on hover so the bundle is in cache by the time the user clicks.
在用户悬停时触发
<link rel="preload">
import()
,这样当用户点击时,包已在缓存中。

3. Server-Side Performance (HIGH)

3. 服务端性能(HIGH)

Authenticate Server Actions like API routes

像API路由一样认证Server Actions

Every
"use server"
function is a public endpoint. Authenticate AND authorize inside the action — never rely on the calling Client Component's gating.
ts
"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 } });
}
每个
"use server"
函数都是一个公共端点。必须在action内部进行认证和授权——绝不能依赖调用方Client Component的权限控制。
ts
"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()
for per-request deduplication

使用
React.cache()
实现单请求内的重复请求去重

ts
import { cache } from "react";

export const getUser = cache(async (id: string) => {
  return db.user.findUnique({ where: { id } });
});
React.cache
dedupes within a single request. Calling
getUser("1")
from three Server Components in the same render = one DB query.
ts
import { cache } from "react";

export const getUser = cache(async (id: string) => {
  return db.user.findUnique({ where: { id } });
});
React.cache
会在单个请求内去重。在同一次渲染中,三个Server Component调用
getUser("1")
只会触发一次数据库查询。

LRU cache for cross-request data

使用LRU缓存跨请求数据

For data that does NOT change per request (config, lookup tables), cache outside React with an LRU cache or
unstable_cache
.
对于不会随请求变化的数据(配置、查找表),在React外部使用LRU缓存或
unstable_cache
进行缓存。

Avoid duplicate serialization in RSC props

避免在RSC props中重复序列化

When a Server Component renders the same data into multiple Client Components, the data is serialized once per consumer. Lift the Client Component up and pass children.
当Server Component将相同数据渲染到多个Client Component时,数据会为每个消费者序列化一次。应将Client Component上移,并传递子组件。

Hoist static I/O to module scope

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

ts
// CORRECT — runs once at module load
const fontData = readFileSync(fontPath);

export async function Page() {
  return <Banner font={fontData} />;
}
ts
// CORRECT — 在模块加载时仅执行一次
const fontData = readFileSync(fontPath);

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

No mutable module-level state in RSC/SSR

RSC/SSR中避免可变模块级状态

Module state on the server is shared across all requests — a race condition between users. Use request-scoped storage (
headers()
,
cookies()
, async context) instead.
服务器上的模块状态会在所有请求之间共享——会导致用户间的竞态条件。应使用请求作用域存储(
headers()
cookies()
、异步上下文)替代。

Minimize data passed to Client Components

最小化传递给Client Components的数据

Only serialize what the Client needs. Strip fields, paginate, project columns at the DB layer.
仅序列化客户端需要的数据。在数据库层剥离字段、分页、投影列。

Parallelize nested fetches with Promise.all per item

使用Promise.all并行处理嵌套获取

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

Use
after()
for non-blocking work

使用
after()
处理非阻塞任务

Next.js 15
after()
runs work after the response is sent — logging, cache warming, analytics.
ts
import { after } from "next/server";
export async function GET() {
  const data = await getData();
  after(() => logAnalytics(data));
  return Response.json(data);
}
Next.js 15的
after()
会在响应发送后执行任务——如日志记录、缓存预热、分析统计。
ts
import { after } from "next/server";
export async function GET() {
  const data = await getData();
  after(() => logAnalytics(data));
  return Response.json(data);
}

4. Client-Side Data Fetching (MEDIUM-HIGH)

4. 客户端数据获取(MEDIUM-HIGH)

SWR / TanStack Query for deduplication

使用SWR/TanStack Query实现请求去重

Multiple components calling
useUser(id)
should share one network request and one cache entry. Use SWR or TanStack Query — never roll your own
useEffect
+
fetch
for shared data.
多个组件调用
useUser(id)
应共享同一个网络请求和缓存条目。使用SWR或TanStack Query——绝不要为共享数据自行实现
useEffect
+
fetch

Deduplicate global event listeners

去重全局事件监听器

tsx
// INCORRECT — every component adds its own
useEffect(() => {
  window.addEventListener("scroll", handler);
  return () => window.removeEventListener("scroll", handler);
}, []);

// CORRECT — single shared listener via a hook + global subject
const useScroll = createScrollHook(); // singleton subject under the hood
tsx
// INCORRECT — 每个组件都会添加自己的监听器
useEffect(() => {
  window.addEventListener("scroll", handler);
  return () => window.removeEventListener("scroll", handler);
}, []);

// CORRECT — 通过hook+全局主题实现单个共享监听器
const useScroll = createScrollHook(); // 底层为单例主题

Passive listeners for scroll

为滚动事件使用被动监听器

ts
window.addEventListener("scroll", handler, { passive: true });
Improves scrolling smoothness; the listener cannot
preventDefault()
.
ts
window.addEventListener("scroll", handler, { passive: true });
提升滚动流畅度;监听器无法调用
preventDefault()

localStorage: version + minimize

localStorage:版本化+最小化存储

  • Always store a
    version
    field; bump on schema change and migrate or discard old data
  • Keep payloads small —
    localStorage
    is synchronous and blocks main thread
  • 始终存储
    version
    字段;当 schema 变更时升级版本,并迁移或丢弃旧数据
  • 保持 payload 体积小巧——
    localStorage
    是同步操作,会阻塞主线程

5. Re-render Optimization (MEDIUM)

5. 重渲染优化(MEDIUM)

Don't subscribe to state used only in callbacks

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

tsx
// INCORRECT — re-renders every time count changes
const count = useStore((s) => s.count);
const handler = () => doSomething(count);

// CORRECT — read once on call
const handler = () => {
  const count = useStore.getState().count;
  doSomething(count);
};
tsx
// INCORRECT — count变化时会触发重渲染
const count = useStore((s) => s.count);
const handler = () => doSomething(count);

// CORRECT — 调用时再读取
const handler = () => {
  const count = useStore.getState().count;
  doSomething(count);
};

Extract expensive work into memoized components

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

tsx
// CORRECT — child re-renders only when `items` changes
const Heavy = memo(function Heavy({ items }: { items: Item[] }) {
  return <Chart data={transform(items)} />;
});
tsx
// CORRECT — 仅当`items`变化时子组件才会重渲染
const Heavy = memo(function Heavy({ items }: { items: Item[] }) {
  return <Chart data={transform(items)} />;
});

Hoist default non-primitive props

将非原始类型的默认props提升到外部

tsx
// INCORRECT — new array each render breaks memo
<List items={items ?? []} />

// CORRECT
const EMPTY: Item[] = [];
<List items={items ?? EMPTY} />
tsx
// INCORRECT — 每次渲染都会创建新数组,破坏记忆化
<List items={items ?? []} />

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

Primitive dependencies in effects

在effect中使用原始类型依赖

tsx
// INCORRECT — new object identity every render
useEffect(() => {}, [{ id, name }]);

// CORRECT — primitives
useEffect(() => {}, [id, name]);
tsx
// INCORRECT — 每次渲染都会创建新对象,导致依赖变化
useEffect(() => {}, [{ id, name }]);

// CORRECT — 使用原始类型
useEffect(() => {}, [id, name]);

Subscribe to derived booleans, not raw values

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

tsx
// INCORRECT — re-renders for any cart change
const cart = useStore((s) => s.cart);
const hasItems = cart.length > 0;

// CORRECT — re-renders only when emptiness flips
const hasItems = useStore((s) => s.cart.length > 0);
tsx
// INCORRECT — 购物车任何变化都会触发重渲染
const cart = useStore((s) => s.cart);
const hasItems = cart.length > 0;

// CORRECT — 仅当购物车从空变非空或反之才会重渲染
const hasItems = useStore((s) => s.cart.length > 0);

Derive during render, never via
useEffect

在渲染时派生值,而非通过
useEffect

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

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

// CORRECT
const full = `${first} ${last}`;

Functional
setState
for stable callbacks

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

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

Lazy state initializer for expensive values

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

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

Avoid memo for simple primitives

避免对简单原始值使用memo

useMemo(() => x + 1, [x])
is overhead. Memo earns its keep on object identity and expensive computation.
useMemo(() => x + 1, [x])
会带来额外开销。memo仅在对象标识和昂贵计算场景下才有价值。

Split hooks with independent deps

拆分具有独立依赖的hooks

tsx
// INCORRECT — both selectors re-run if either source changes
const { a, b } = useSomething(source1, source2);

// CORRECT
const a = useA(source1);
const b = useB(source2);
tsx
// INCORRECT — 任一源变化时两个选择器都会重新运行
const { a, b } = useSomething(source1, source2);

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

Move interaction logic into event handlers

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

Event handlers run only on the user action —
useEffect
re-runs whenever deps change.
事件处理器仅在用户操作时运行——
useEffect
会在依赖变化时重新运行。

startTransition
for non-urgent updates

使用
startTransition
处理非紧急更新

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

useDeferredValue
for expensive renders

使用
useDeferredValue
处理昂贵渲染

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

useRef
for transient frequent values

使用
useRef
存储频繁变化的临时值

For values that change often but should not trigger re-render (timestamps, last-key, accumulators).
适用于经常变化但不应触发重渲染的值(时间戳、最后按键值、累加器)。

Don't define components inside components

不要在组件内部定义组件

tsx
// INCORRECT — Inner is a new component on every Outer render
function Outer() {
  const Inner = () => <span />;
  return <Inner />;
}
Each render makes a new
Inner
type, defeating reconciliation and unmounting children.
tsx
// INCORRECT — 每次Outer渲染时都会创建新的Inner组件
function Outer() {
  const Inner = () => <span />;
  return <Inner />;
}
每次渲染都会创建新的
Inner
类型,破坏协调过程并卸载子组件。

6. Rendering Performance (MEDIUM)

6. 渲染性能(MEDIUM)

Animate the wrapper, not the SVG

动画包裹元素,而非SVG本身

Transforming a
<div>
wrapper around an SVG is GPU-accelerated; transforming the SVG itself triggers paint.
对SVG周围的
<div>
包裹元素进行变换是GPU加速的;变换SVG本身会触发绘制操作。

content-visibility: auto
for long lists

对长列表使用
content-visibility: auto

css
.row { content-visibility: auto; contain-intrinsic-size: auto 80px; }
Browser skips offscreen rendering — major win for lists with hundreds of rows.
css
.row { content-visibility: auto; contain-intrinsic-size: auto 80px; }
浏览器会跳过屏幕外元素的渲染——对包含数百行的列表提升显著。

Hoist static JSX

将静态JSX提升到外部

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

SVG: reduce coordinate precision

SVG:降低坐标精度

d="M10.123456,20.654321"
d="M10.12,20.65"
. Each digit costs bytes; the visual difference is sub-pixel.
d="M10.123456,20.654321"
d="M10.12,20.65"
。每个数字都会占用字节;视觉差异在亚像素级别。

Hydration no-flicker via inline script

通过内联脚本实现无闪烁hydration

For values needed before hydration (theme, locale), inline a
<script>
that sets
document.documentElement.dataset.*
before React mounts.
对于hydration前需要的值(主题、区域设置),在React挂载前内联
<script>
设置
document.documentElement.dataset.*

Suppress expected hydration mismatches narrowly

精准抑制预期的hydration不匹配

tsx
<time suppressHydrationWarning>{new Date().toLocaleString()}</time>
Use ONLY for known-divergent leaf nodes — never on a tree containing other children.
tsx
<time suppressHydrationWarning>{new Date().toLocaleString()}</time>
仅用于已知会出现差异的叶子节点——绝不要用于包含其他子节点的树。

<Activity>
for show/hide instead of mount/unmount

使用
<Activity>
实现显示/隐藏,而非挂载/卸载

React 19
<Activity mode="visible|hidden">
keeps tree state and effects mounted but hides — cheaper than unmount/remount for tabs and accordions.
React 19的
<Activity mode="visible|hidden">
会保持树的状态和effect挂载但隐藏——对于标签页和折叠面板,比卸载/重新挂载成本更低。

Ternary over
&&
for conditional render

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

tsx
// INCORRECT — `0` renders as text node
{count && <Badge>{count}</Badge>}

// CORRECT
{count > 0 ? <Badge>{count}</Badge> : null}
tsx
// INCORRECT — `0`会被渲染为文本节点
{count && <Badge>{count}</Badge>}

// CORRECT
{count > 0 ? <Badge>{count}</Badge> : null}

useTransition
for loading states

使用
useTransition
处理加载状态

Pair
startTransition
with the action; React shows the previous UI as
isPending
while the next state computes.
startTransition
与操作配对;React会在计算下一个状态时显示之前的UI,并标记
isPending

React DOM resource hints

React DOM资源提示

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

defer
/
async
on
<script>
tags

<script>
标签上使用
defer
/
async

defer
for ordered execution after DOMContentLoaded;
async
for fire-and-forget.
defer
用于DOMContentLoaded后按顺序执行;
async
用于无需等待的脚本。

7. JavaScript Performance (LOW-MEDIUM)

7. JavaScript性能(LOW-MEDIUM)

  • Batch DOM/CSS changes — apply via class swap or
    cssText
    , not property-by-property
  • Map
    for repeated lookups
    O(1)
    vs
    O(n)
    linear scan
  • Cache property access in loops
    const len = arr.length
  • Memoize pure functions — module-level
    Map<key, result>
  • Cache
    localStorage
    reads
    — sync API; one read per render
  • Combine
    filter().map()
    into one pass
    flatMap
    or single
    for
  • Check array length first before expensive comparisons
  • Early return from functions
  • Hoist RegExp out of loops — compilation is not free
  • Loop for min/max instead of
    sort()
    O(n)
    vs
    O(n log n)
  • Set
    /
    Map
    for membership
    O(1)
    vs
    Array.includes
    O(n)
  • toSorted()
    over mutation
    when immutability matters
  • flatMap
    to map and filter in one pass
  • requestIdleCallback
    for non-critical work
  • 批量处理DOM/CSS变更 — 通过类切换或
    cssText
    应用,而非逐个修改属性
  • 使用
    Map
    进行重复查找
    O(1)
    时间复杂度 vs 数组线性扫描的
    O(n)
  • 在循环中缓存属性访问
    const len = arr.length
  • 记忆化纯函数 — 使用模块级
    Map<key, result>
  • 缓存
    localStorage
    读取结果
    — 同步API;每次渲染仅读取一次
  • filter().map()
    合并为单次遍历
    — 使用
    flatMap
    或单个
    for
    循环
  • 在进行昂贵比较前先检查数组长度
  • 函数提前返回
  • 将正则表达式提升到循环外部 — 编译过程有开销
  • 使用循环查找最小值/最大值
    O(n)
    时间复杂度 vs
    sort()
    O(n log n)
  • 使用
    Set
    /
    Map
    判断成员关系
    O(1)
    时间复杂度 vs
    Array.includes
    O(n)
  • 当需要不可变性时使用
    toSorted()
    而非 mutation
  • 使用
    flatMap
    在单次遍历中完成映射和过滤
  • 使用
    requestIdleCallback
    处理非关键任务

8. Advanced Patterns (LOW)

8. 高级模式(LOW)

useEffectEvent
deps

useEffectEvent
的依赖

Values from
useEffectEvent
are stable — do NOT add them to effect deps.
useEffectEvent
的值是稳定的——不要将它们添加到effect的依赖中。

Event handler refs

事件处理器refs

For stable callbacks passed to memoized children:
tsx
const handlerRef = useRef(handler);
useEffect(() => { handlerRef.current = handler; });
const stable = useCallback((arg) => handlerRef.current(arg), []);
用于向记忆化子组件传递稳定回调:
tsx
const handlerRef = useRef(handler);
useEffect(() => { handlerRef.current = handler; });
const stable = useCallback((arg) => handlerRef.current(arg), []);

Init once per app load

应用加载时仅初始化一次

For module-level singletons (telemetry, logger), guard with a module-scope flag — not
useEffect
.
对于模块级单例(遥测、日志器),使用模块作用域标志进行保护——而非
useEffect

useLatest
for stable callback refs

使用
useLatest
获取稳定的回调refs

tsx
function useLatest<T>(value: T) {
  const ref = useRef(value);
  ref.current = value;
  return ref;
}
tsx
function useLatest<T>(value: T) {
  const ref = useRef(value);
  ref.current = value;
  return ref;
}

Automated Tools

自动化工具

Many of these rules are now automated:
  • Next.js 13.5+ Optimize Package Imports — barrel import optimization
  • React Compiler (RFC, in canary) — auto-memoization
  • Turbopack — faster builds, better tree-shaking
  • Bundle Analyzer (
    @next/bundle-analyzer
    ) — visualize first-load JS
When the project ships React Compiler, demote
rerender-*
manual memoization rules to "review-only" — the compiler handles them. Manual
useMemo
/
useCallback
becomes unnecessary noise.
许多规则现已实现自动化:
  • Next.js 13.5+ Optimize Package Imports — 桶式导入优化
  • React Compiler(RFC,预览版) — 自动记忆化
  • Turbopack — 更快的构建,更优的tree-shaking
  • Bundle Analyzer (
    @next/bundle-analyzer
    ) — 可视化首屏加载JS
当项目启用React Compiler后,可将
rerender-*
手动记忆化规则降级为"仅评审"——编译器会自动处理这些规则。手动
useMemo
/
useCallback
会变成不必要的冗余代码。

Lighthouse / Web Vitals Mapping

Lighthouse / Web Vitals映射

MetricMost relevant categories
LCP (Largest Contentful Paint)Waterfalls, Bundle Size, Resource Hints
INP (Interaction to Next Paint)Re-render, Rendering, JavaScript
CLS (Cumulative Layout Shift)Rendering (Suspense placement, image dimensions)
TBT (Total Blocking Time)Bundle Size, JavaScript, Defer Third-Party
FID (legacy)Bundle Size, Hydration
指标最相关的类别
LCP(最大内容绘制)请求瀑布、包体积、资源提示
INP(交互到下一次绘制的时间)重渲染、渲染性能、JavaScript
CLS(累积布局偏移)渲染性能(Suspense位置、图片尺寸)
TBT(总阻塞时间)包体积、JavaScript、第三方脚本延迟加载
FID(旧指标)包体积、Hydration

Related

相关资源

  • Skills: react-patterns, react-testing, frontend-patterns, accessibility, nextjs-turbopack
  • Rules: rules/react/
  • Agents:
    react-reviewer
    enforces these rules in code review;
    react-build-resolver
    handles related build failures
  • Commands:
    /react-review
    ,
    /react-build
    ,
    /react-test
  • 技能:react-patterns, react-testing, frontend-patterns, accessibility, nextjs-turbopack
  • 规则:rules/react/
  • Agent:
    react-reviewer
    在代码评审中强制执行这些规则;
    react-build-resolver
    处理相关构建失败
  • 命令:
    /react-review
    ,
    /react-build
    ,
    /react-test

Attribution

版权说明

Adapted from Vercel Labs
react-best-practices
skill (MIT License, copyright Vercel Engineering, v1.0.0 January 2026). Source: https://github.com/vercel-labs/agent-skills/tree/main/skills/react-best-practices.
This skill restructures and adapts the original 70-rule catalog into a single navigable reference. For the full original ruleset with extended examples, see the upstream repository.
改编自Vercel Labs的
react-best-practices
技能(MIT许可证,Vercel Engineering版权所有,2026年1月v1.0.0版本)。源地址:https://github.com/vercel-labs/agent-skills/tree/main/skills/react-best-practices
本技能将原始的70条规则目录重组为单一可导航的参考文档。如需查看包含扩展示例的完整原始规则集,请访问上游仓库。