react-performance
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReact Performance
React 性能优化
Performance optimization patterns for React 18/19 and Next.js, adapted from Vercel Labs (MIT, v1.0.0). This skill organizes rules by priority and provides decision-tree guidance for active code review and refactoring.
react-best-practices适用于React 18/19和Next.js的性能优化模式,改编自Vercel Labs的(MIT许可证,v1.0.0版本)。本技能按优先级整理规则,并为代码评审和重构提供决策树指导。
react-best-practicesWhen 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/, or data layerscomponents/
- 编写或评审React/Next.js代码以优化性能时
- 诊断页面加载缓慢、交互卡顿或客户端CPU占用过高问题时
- 审计包体积或Lighthouse核心Web指标回归问题时
- 消除Server Components/API路由中的请求瀑布时
- 减少客户端重渲染次数时
- 优化长列表、动画或hydration(水合)过程时
- 审计涉及、
app/、pages/或数据层的PR中的优化方案时components/
Priority Index
优先级索引
| Priority | Category | Prefix | When it matters |
|---|---|---|---|
| 1 — CRITICAL | Eliminating Waterfalls | | Anytime |
| 2 — CRITICAL | Bundle Size Optimization | | First-load JS, route-level imports, third-party libs |
| 3 — HIGH | Server-Side Performance | | RSC, Server Actions, API routes, SSR |
| 4 — MEDIUM-HIGH | Client-Side Data Fetching | | SWR / TanStack Query / raw |
| 5 — MEDIUM | Re-render Optimization | | High-frequency state updates, parent-child fan-out |
| 6 — MEDIUM | Rendering Performance | | Long lists, animations, hydration |
| 7 — LOW-MEDIUM | JavaScript Performance | | Hot loops, frequent allocations |
| 8 — LOW | Advanced Patterns | | Effect-event integration, stable refs |
| 优先级 | 类别 | 前缀 | 适用场景 |
|---|---|---|---|
| 1 — 关键 | 消除请求瀑布 | | 任何 |
| 2 — 关键 | 包体积优化 | | 首屏加载JS、路由级导入、第三方库相关场景 |
| 3 — 高 | 服务端性能 | | RSC、Server Actions、API路由、SSR相关场景 |
| 4 — 中高 | 客户端数据获取 | | Hooks中使用SWR/TanStack Query/原生 |
| 5 — 中 | 重渲染优化 | | 高频状态更新、父子组件扇出场景 |
| 6 — 中 | 渲染性能 | | 长列表、动画、hydration相关场景 |
| 7 — 中低 | JavaScript性能 | | 热循环、频繁内存分配场景 |
| 8 — 低 | 高级模式 | | Effect与事件集成、稳定refs相关场景 |
1. Eliminating Waterfalls (CRITICAL)
1. 消除请求瀑布(CRITICAL)
"Waterfalls are the #1 performance killer" — every sequentialadds full network latency.await
"请求瀑布是性能的头号杀手" — 每个顺序执行的都会增加完整的网络延迟。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 into the branch that uses it.
awaitts
// 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);将移到需要使用数据的分支中。
awaitts
// 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 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 ).
<Suspense>min-height将边界靠近数据获取逻辑,这样页面可以先渲染已有内容,较慢的子树在流式加载过程中逐步呈现。权衡点:内容加载时会出现布局偏移——需预留空间(骨架屏或)。
<Suspense>min-heightServer 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 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.
index.tsts
// 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.
桶式文件会迫使打包工具遍历整个模块图,即使tree-shaking移除了大部分内容。直接导入在许多实际应用中可节省200-800ms的首屏加载JS时间。
index.tsts
// 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 with (default) or .
next/scriptstrategy="afterInteractive""lazyOnload"在hydration完成后加载分析、日志、支持小部件等脚本。使用并设置(默认值)或。
next/scriptstrategy="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 or on hover so the bundle is in cache by the time the user clicks.
<link rel="preload">import()在用户悬停时触发或,这样当用户点击时,包已在缓存中。
<link rel="preload">import()3. Server-Side Performance (HIGH)
3. 服务端性能(HIGH)
Authenticate Server Actions like API routes
像API路由一样认证Server Actions
Every function is a public endpoint. Authenticate AND authorize inside the action — never rely on the calling Client Component's gating.
"use server"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 } });
}每个函数都是一个公共端点。必须在action内部进行认证和授权——绝不能依赖调用方Client Component的权限控制。
"use server"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()使用React.cache()
实现单请求内的重复请求去重
React.cache()ts
import { cache } from "react";
export const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } });
});React.cachegetUser("1")ts
import { cache } from "react";
export const getUser = cache(async (id: string) => {
return db.user.findUnique({ where: { id } });
});React.cachegetUser("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_cacheAvoid 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 (, , async context) instead.
headers()cookies()服务器上的模块状态会在所有请求之间共享——会导致用户间的竞态条件。应使用请求作用域存储(、、异步上下文)替代。
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()使用after()
处理非阻塞任务
after()Next.js 15 runs work after the response is sent — logging, cache warming, analytics.
after()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 should share one network request and one cache entry. Use SWR or TanStack Query — never roll your own + for shared data.
useUser(id)useEffectfetch多个组件调用应共享同一个网络请求和缓存条目。使用SWR或TanStack Query——绝不要为共享数据自行实现+。
useUser(id)useEffectfetchDeduplicate 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 hoodtsx
// 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 field; bump on schema change and migrate or discard old data
version - Keep payloads small — is synchronous and blocks main thread
localStorage
- 始终存储字段;当 schema 变更时升级版本,并迁移或丢弃旧数据
version - 保持 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在渲染时派生值,而非通过useEffect
useEffecttsx
// 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使用函数式setState
获取稳定回调
setStatetsx
// 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])useMemo(() => x + 1, [x])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 — re-runs whenever deps change.
useEffect事件处理器仅在用户操作时运行——会在依赖变化时重新运行。
useEffectstartTransition
for non-urgent updates
startTransition使用startTransition
处理非紧急更新
startTransitiontsx
const [pending, startTransition] = useTransition();
startTransition(() => setFilters(newFilters));tsx
const [pending, startTransition] = useTransition();
startTransition(() => setFilters(newFilters));useDeferredValue
for expensive renders
useDeferredValue使用useDeferredValue
处理昂贵渲染
useDeferredValuetsx
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使用useRef
存储频繁变化的临时值
useRefFor 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 type, defeating reconciliation and unmounting children.
Innertsx
// INCORRECT — 每次Outer渲染时都会创建新的Inner组件
function Outer() {
const Inner = () => <span />;
return <Inner />;
}每次渲染都会创建新的类型,破坏协调过程并卸载子组件。
Inner6. Rendering Performance (MEDIUM)
6. 渲染性能(MEDIUM)
Animate the wrapper, not the SVG
动画包裹元素,而非SVG本身
Transforming a wrapper around an SVG is GPU-accelerated; transforming the SVG itself triggers paint.
<div>对SVG周围的包裹元素进行变换是GPU加速的;变换SVG本身会触发绘制操作。
<div>content-visibility: auto
for long lists
content-visibility: auto对长列表使用content-visibility: auto
content-visibility: autocss
.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"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 that sets before React mounts.
<script>document.documentElement.dataset.*对于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>使用<Activity>
实现显示/隐藏,而非挂载/卸载
<Activity>React 19 keeps tree state and effects mounted but hides — cheaper than unmount/remount for tabs and accordions.
<Activity mode="visible|hidden">React 19的会保持树的状态和effect挂载但隐藏——对于标签页和折叠面板,比卸载/重新挂载成本更低。
<Activity mode="visible|hidden">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使用useTransition
处理加载状态
useTransitionPair with the action; React shows the previous UI as while the next state computes.
startTransitionisPending将与操作配对;React会在计算下一个状态时显示之前的UI,并标记。
startTransitionisPendingReact 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
deferasync<script>在<script>
标签上使用defer
/async
<script>deferasyncdeferasyncdeferasync7. JavaScript Performance (LOW-MEDIUM)
7. JavaScript性能(LOW-MEDIUM)
- Batch DOM/CSS changes — apply via class swap or , not property-by-property
cssText - for repeated lookups —
MapvsO(1)linear scanO(n) - Cache property access in loops —
const len = arr.length - Memoize pure functions — module-level
Map<key, result> - Cache reads — sync API; one read per render
localStorage - Combine into one pass —
filter().map()or singleflatMapfor - 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()vsO(n)O(n log n) - /
Setfor membership —MapvsO(1)Array.includesO(n) - over mutation when immutability matters
toSorted() - to map and filter in one pass
flatMap - for non-critical work
requestIdleCallback
- 批量处理DOM/CSS变更 — 通过类切换或应用,而非逐个修改属性
cssText - 使用进行重复查找 —
Map时间复杂度 vs 数组线性扫描的O(1)O(n) - 在循环中缓存属性访问 —
const len = arr.length - 记忆化纯函数 — 使用模块级
Map<key, result> - 缓存读取结果 — 同步API;每次渲染仅读取一次
localStorage - 将合并为单次遍历 — 使用
filter().map()或单个flatMap循环for - 在进行昂贵比较前先检查数组长度
- 函数提前返回
- 将正则表达式提升到循环外部 — 编译过程有开销
- 使用循环查找最小值/最大值 — 时间复杂度 vs
O(n)的sort()O(n log n) - 使用/
Set判断成员关系 —Map时间复杂度 vsO(1)的Array.includesO(n) - 当需要不可变性时使用而非 mutation
toSorted() - 使用在单次遍历中完成映射和过滤
flatMap - 使用处理非关键任务
requestIdleCallback
8. Advanced Patterns (LOW)
8. 高级模式(LOW)
useEffectEvent
deps
useEffectEventuseEffectEvent
的依赖
useEffectEventValues from are stable — do NOT add them to effect deps.
useEffectEventuseEffectEventEvent 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对于模块级单例(遥测、日志器),使用模块作用域标志进行保护——而非。
useEffectuseLatest
for stable callback refs
useLatest使用useLatest
获取稳定的回调refs
useLatesttsx
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 () — visualize first-load JS
@next/bundle-analyzer
When the project ships React Compiler, demote manual memoization rules to "review-only" — the compiler handles them. Manual / becomes unnecessary noise.
rerender-*useMemouseCallback许多规则现已实现自动化:
- Next.js 13.5+ Optimize Package Imports — 桶式导入优化
- React Compiler(RFC,预览版) — 自动记忆化
- Turbopack — 更快的构建,更优的tree-shaking
- Bundle Analyzer () — 可视化首屏加载JS
@next/bundle-analyzer
当项目启用React Compiler后,可将手动记忆化规则降级为"仅评审"——编译器会自动处理这些规则。手动/会变成不必要的冗余代码。
rerender-*useMemouseCallbackLighthouse / Web Vitals Mapping
Lighthouse / Web Vitals映射
| Metric | Most 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: enforces these rules in code review;
react-reviewerhandles related build failuresreact-build-resolver - 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 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.
react-best-practicesThis 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的技能(MIT许可证,Vercel Engineering版权所有,2026年1月v1.0.0版本)。源地址:https://github.com/vercel-labs/agent-skills/tree/main/skills/react-best-practices。
react-best-practices本技能将原始的70条规则目录重组为单一可导航的参考文档。如需查看包含扩展示例的完整原始规则集,请访问上游仓库。