react-performance

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

React Performance

React性能优化

Systematic performance optimization for React and Next.js applications, organized by impact.
为React和Next.js应用提供按影响优先级排序的系统性性能优化方案。

Priority 1: Eliminate Waterfalls (CRITICAL)

优先级1:消除请求瀑布流(CRITICAL,关键)

Sequential async operations are the single biggest performance killer. Fix these first.
串行异步操作是性能的最大杀手,应优先修复。

Defer Await

延迟等待

Move
await
to the point of use, not the point of declaration.
tsx
// BAD: Sequential — total time = fetch1 + fetch2
async function Page() {
  const user = await getUser();
  const posts = await getPosts(user.id);
  return <Feed user={user} posts={posts} />;
}

// GOOD: Parallel where possible
async function Page() {
  const userPromise = getUser();
  const postsPromise = getPosts(); // if independent
  const [user, posts] = await Promise.all([userPromise, postsPromise]);
  return <Feed user={user} posts={posts} />;
}
await
移至使用点,而非声明点。
tsx
// 不良写法:串行执行 — 总耗时 = fetch1 + fetch2
async function Page() {
  const user = await getUser();
  const posts = await getPosts(user.id);
  return <Feed user={user} posts={posts} />;
}

// 良好写法:尽可能并行执行
async function Page() {
  const userPromise = getUser();
  const postsPromise = getPosts(); // 若相互独立
  const [user, posts] = await Promise.all([userPromise, postsPromise]);
  return <Feed user={user} posts={posts} />;
}

Suspense Streaming

Suspense流式渲染

Wrap slow data behind
<Suspense>
so the shell renders instantly.
tsx
export default function Page() {
  return (
    <main>
      <Header />              {/* instant */}
      <Suspense fallback={<Skeleton />}>
        <SlowDataSection />   {/* streams in */}
      </Suspense>
    </main>
  );
}
将加载缓慢的数据用
<Suspense>
包裹,让页面框架立即渲染。
tsx
export default function Page() {
  return (
    <main>
      <Header />              {/* 立即渲染 */}
      <Suspense fallback={<Skeleton />}>
        <SlowDataSection />   {/* 流式加载 */}
      </Suspense>
    </main>
  );
}

Partial Dependencies

部分依赖处理

When promises have partial dependencies, start independent work immediately.
tsx
async function Dashboard() {
  const userPromise = getUser();
  const settingsPromise = getSettings(); // independent

  const user = await userPromise;
  const postsPromise = getPosts(user.id); // depends on user

  const [settings, posts] = await Promise.all([settingsPromise, postsPromise]);
  return <View user={user} settings={settings} posts={posts} />;
}
当Promise存在部分依赖时,立即启动独立任务。
tsx
async function Dashboard() {
  const userPromise = getUser();
  const settingsPromise = getSettings(); // 独立任务

  const user = await userPromise;
  const postsPromise = getPosts(user.id); // 依赖user

  const [settings, posts] = await Promise.all([settingsPromise, postsPromise]);
  return <View user={user} settings={settings} posts={posts} />;
}

Priority 2: Bundle Size (CRITICAL)

优先级2:减小打包体积(CRITICAL,关键)

Direct Imports

直接导入

Never import from barrel files in production code.
tsx
// BAD: Pulls entire library
import { Button } from '@/components';
import { format } from 'date-fns';

// GOOD: Tree-shakeable
import { Button } from '@/components/ui/button';
import { format } from 'date-fns/format';
生产代码中绝不要从桶文件(barrel files)导入。
tsx
// 不良写法:引入整个库
import { Button } from '@/components';
import { format } from 'date-fns';

// 良好写法:支持树摇优化
import { Button } from '@/components/ui/button';
import { format } from 'date-fns/format';

Dynamic Imports

动态导入

Code-split heavy components that aren't needed on initial render.
tsx
import dynamic from 'next/dynamic';

const Chart = dynamic(() => import('@/components/chart'), {
  loading: () => <ChartSkeleton />,
  ssr: false,  // skip SSR for client-only components
});

const Editor = dynamic(() => import('@/components/editor'), {
  loading: () => <EditorSkeleton />,
});
对初始渲染不需要的重型组件进行代码分割。
tsx
import dynamic from 'next/dynamic';

const Chart = dynamic(() => import('@/components/chart'), {
  loading: () => <ChartSkeleton />,
  ssr: false,  // 对仅客户端组件跳过SSR
});

const Editor = dynamic(() => import('@/components/editor'), {
  loading: () => <EditorSkeleton />,
});

Defer Third-Party Scripts

延迟加载第三方脚本

Load analytics, logging, and non-critical scripts after hydration.
tsx
// BAD: Blocks hydration
import { analytics } from 'heavy-analytics';
analytics.init();

// GOOD: Load after hydration
useEffect(() => {
  import('heavy-analytics').then(({ analytics }) => analytics.init());
}, []);
在 hydration(水合)完成后再加载分析、日志等非关键脚本。
tsx
// 不良写法:阻塞水合
import { analytics } from 'heavy-analytics';
analytics.init();

// 良好写法:水合完成后加载
useEffect(() => {
  import('heavy-analytics').then(({ analytics }) => analytics.init());
}, []);

Preload on Intent

基于用户意图预加载

Preload resources when user shows intent (hover, focus).
tsx
function NavLink({ href, children }: { href: string; children: React.ReactNode }) {
  const router = useRouter();
  return (
    <Link
      href={href}
      onMouseEnter={() => router.prefetch(href)}
      onFocus={() => router.prefetch(href)}
    >
      {children}
    </Link>
  );
}
当用户表现出意图(如悬停、聚焦)时预加载资源。
tsx
function NavLink({ href, children }: { href: string; children: React.ReactNode }) {
  const router = useRouter();
  return (
    <Link
      href={href}
      onMouseEnter={() => router.prefetch(href)}
      onFocus={() => router.prefetch(href)}
    >
      {children}
    </Link>
  );
}

Priority 3: Server-Side Performance (HIGH)

优先级3:服务端性能(HIGH,高)

Request-Scoped Deduplication

请求作用域内的请求去重

Use
React.cache()
to deduplicate data fetches within a single request.
tsx
import { cache } from 'react';

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

// Called in layout.tsx AND page.tsx — only one DB query
使用
React.cache()
在单个请求内对数据查询进行去重。
tsx
import { cache } from 'react';

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

// 在layout.tsx和page.tsx中都调用 — 仅执行一次数据库查询

Minimize Serialization

最小化序列化

Only pass the data client components actually need.
tsx
// BAD: Serializes entire user object
<ClientAvatar user={user} />

// GOOD: Pass only what's needed
<ClientAvatar name={user.name} avatarUrl={user.avatarUrl} />
仅传递客户端组件实际需要的数据。
tsx
// 不良写法:序列化整个用户对象
<ClientAvatar user={user} />

// 良好写法:仅传递所需数据
<ClientAvatar name={user.name} avatarUrl={user.avatarUrl} />

Non-Blocking Background Work

非阻塞后台任务

Use
after()
for work that shouldn't block the response.
tsx
import { after } from 'next/server';

export async function POST(request: Request) {
  const data = await processRequest(request);

  after(async () => {
    await logAnalytics(data);
    await sendNotification(data);
  });

  return Response.json(data); // returns immediately
}
使用
after()
处理不应阻塞响应的任务。
tsx
import { after } from 'next/server';

export async function POST(request: Request) {
  const data = await processRequest(request);

  after(async () => {
    await logAnalytics(data);
    await sendNotification(data);
  });

  return Response.json(data); // 立即返回响应
}

Priority 4: Re-render Prevention (MEDIUM)

优先级4:避免重复渲染(MEDIUM,中)

Derived State

派生状态

Compute derived values during render, never in effects.
tsx
// BAD: Extra render cycle
const [items, setItems] = useState([]);
const [count, setCount] = useState(0);
useEffect(() => setCount(items.length), [items]);

// GOOD: Derive during render
const [items, setItems] = useState([]);
const count = items.length;
在渲染期间计算派生值,绝不在effect中计算。
tsx
// 不良写法:额外触发一次渲染周期
const [items, setItems] = useState([]);
const [count, setCount] = useState(0);
useEffect(() => setCount(items.length), [items]);

// 良好写法:在渲染期间派生
const [items, setItems] = useState([]);
const count = items.length;

Stable References

稳定引用

Use callback-based setState and extract callbacks outside render.
tsx
// BAD: New function every render
<Button onClick={() => setCount(count + 1)} />

// GOOD: Stable reference
<Button onClick={() => setCount(c => c + 1)} />
使用回调式setState并将回调提取到渲染外部。
tsx
// 不良写法:每次渲染都创建新函数
<Button onClick={() => setCount(count + 1)} />

// 良好写法:引用稳定
<Button onClick={() => setCount(c => c + 1)} />

Primitive Dependencies

原始类型依赖项

Use primitives in dependency arrays to avoid false positives.
tsx
// BAD: Object reference changes every render
useEffect(() => { ... }, [config]);

// GOOD: Primitive values are stable
useEffect(() => { ... }, [config.apiUrl, config.timeout]);
在依赖数组中使用原始类型以避免误触发。
tsx
// 不良写法:对象引用每次渲染都变化
useEffect(() => { ... }, [config]);

// 良好写法:原始类型值稳定
useEffect(() => { ... }, [config.apiUrl, config.timeout]);

Lazy State Initialization

惰性状态初始化

Pass initializer functions for expensive initial state.
tsx
// BAD: Runs every render
const [data, setData] = useState(expensiveParse(raw));

// GOOD: Runs only on mount
const [data, setData] = useState(() => expensiveParse(raw));
为昂贵的初始状态传递初始化函数。
tsx
// 不良写法:每次渲染都执行
const [data, setData] = useState(expensiveParse(raw));

// 良好写法:仅在挂载时执行
const [data, setData] = useState(() => expensiveParse(raw));

Memoize Expensive Components

Memoize 昂贵组件

Extract heavy computation into memoized child components.
tsx
const ExpensiveList = memo(function ExpensiveList({ items }: { items: Item[] }) {
  return items.map(item => <ComplexItem key={item.id} item={item} />);
});
将重型计算提取到已记忆的子组件中。
tsx
const ExpensiveList = memo(function ExpensiveList({ items }: { items: Item[] }) {
  return items.map(item => <ComplexItem key={item.id} item={item} />);
});

useTransition for Deferrable Updates

使用useTransition处理可延迟更新

Use
startTransition
for non-urgent state updates.
tsx
const [isPending, startTransition] = useTransition();

function handleSearch(query: string) {
  setInputValue(query);                    // urgent: update input
  startTransition(() => setResults(search(query))); // deferrable: update results
}
使用
startTransition
处理非紧急状态更新。
tsx
const [isPending, startTransition] = useTransition();

function handleSearch(query: string) {
  setInputValue(query);                    // 紧急:更新输入框
  startTransition(() => setResults(search(query))); // 可延迟:更新搜索结果
}

Priority 5: Rendering Performance (LOW-MEDIUM)

优先级5:渲染性能(LOW-MEDIUM,低-中)

Content Visibility

内容可见性

Apply CSS
content-visibility
for long scrollable lists.
css
.list-item {
  content-visibility: auto;
  contain-intrinsic-size: 0 80px;
}
对长滚动列表应用CSS
content-visibility
属性。
css
.list-item {
  content-visibility: auto;
  contain-intrinsic-size: 0 80px;
}

Hoist Static JSX

提升静态JSX

Extract JSX that doesn't depend on props/state outside the component.
tsx
// BAD: Recreated every render
function Layout({ children }) {
  return (
    <div>
      <footer><p>Copyright 2026</p></footer>
      {children}
    </div>
  );
}

// GOOD: Created once
const footer = <footer><p>Copyright 2026</p></footer>;
function Layout({ children }) {
  return <div>{footer}{children}</div>;
}
将不依赖props/state的JSX提取到组件外部。
tsx
// 不良写法:每次渲染都重新创建
function Layout({ children }) {
  return (
    <div>
      <footer><p>Copyright 2026</p></footer>
      {children}
    </div>
  );
}

// 良好写法:仅创建一次
const footer = <footer><p>Copyright 2026</p></footer>;
function Layout({ children }) {
  return <div>{footer}{children}</div>;
}

Conditional Rendering

条件渲染

Prefer ternary over
&&
to avoid rendering
0
or
""
.
tsx
// BAD: Renders "0" when count is 0
{count && <Badge count={count} />}

// GOOD: Explicit boolean check
{count > 0 ? <Badge count={count} /> : null}
优先使用三元表达式而非
&&
,避免渲染
0
或空字符串
""
tsx
// 不良写法:当count为0时渲染"0"
{count && <Badge count={count} />}

// 良好写法:显式布尔检查
{count > 0 ? <Badge count={count} /> : null}

Quick Reference

快速参考

IssueFixPriority
Sequential fetches
Promise.all()
/ Suspense
CRITICAL
Barrel importsDirect path importsCRITICAL
Large initial bundle
next/dynamic
CRITICAL
Redundant DB calls
React.cache()
HIGH
Over-serialized propsPass primitives onlyHIGH
Derived state in useEffectCompute during renderMEDIUM
Unstable callbacks
useCallback
/ callback setState
MEDIUM
Long listsVirtualization +
content-visibility
MEDIUM
Non-urgent updates
useTransition
MEDIUM
问题修复方案优先级
串行请求
Promise.all()
/ Suspense
CRITICAL
桶文件导入使用直接路径导入CRITICAL
初始打包体积过大
next/dynamic
CRITICAL
冗余数据库调用
React.cache()
HIGH
过度序列化的props仅传递原始类型HIGH
在useEffect中计算派生状态在渲染期间计算MEDIUM
不稳定的回调函数
useCallback
/ 回调式setState
MEDIUM
长列表虚拟化 +
content-visibility
MEDIUM
非紧急更新
useTransition
MEDIUM