Communitygithub.com

react-patterns

React 18/19 模式,包含 Hooks 規範、伺服器/客戶端元件邊界、懸掛與錯誤邊界、表單動作、資料獲取、狀態管理決策樹以及無障礙優先的組合。適用於編寫或審查 React 元件時。

react-patterns 是什麼?

這項技能提供了一套完整的 React 18 與 19 的慣用模式,旨在協助開發者建立穩健、易於存取且高效能的元件樹。它涵蓋了核心的 React 原則,例如將渲染視為 Props 與 State 的純函數、在渲染體外部處理副作用,以及使用組合而非繼承來構建 UI。此外,技能中詳細說明了 Hooks 的規範、狀態位置的決策樹(useState、提升狀態、Context、外部 Store)、以及針對 Next.js App Router 的伺服器元件(RSC)與客戶端元件的邊界處理方式,並提供具體的程式碼範例。技能也深入探討了 Suspense 與 Error Boundaries 的正確使用情境、React 19 的表單動作(useActionState)、與資料獲取決策矩陣(RSC、TanStack Query、SWR)。最後,它還包含了無障礙優先的組合方法、效能優化建議(如 React.memo 的使用時機)、以及常見的組合模式(如 children、具名插槽、複合元件與 Render Props)。這項技能適用於所有主要的 React 框架(React Router、Next.js、Remix),但明確指出特定框架(如 Next.js App Router)的細節與 React Native 的特定模式不在此範圍內。

相容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/react-patterns

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

React 模式

適用於 React 18/19 的慣用模式,幫助您建構穩健、易於存取且高效能的元件樹。

何時啟用

  • 編寫或修改 React 函式元件、自訂 Hook 或元件樹
  • 審查 JSX/TSX 檔案
  • 設計狀態形狀或元件組合方式
  • 遷移類別元件或大量使用 forwardRef/useEffect 的舊有程式碼
  • 在本地狀態、提升狀態、Context 與外部 Store 之間做出選擇
  • 處理伺服器元件 / 客戶端元件(Next.js App Router、RSC)
  • 使用 React 19 動作或受控輸入來實作表單
  • 使用 TanStack Query / SWR / RSC 來串接資料獲取

核心原則

1. 渲染是 Props 與 State 的純函數

// 好的做法:在渲染期間推導
function Cart({ items }: { items: CartItem[] }) {
  const total = items.reduce((sum, i) => sum + i.price * i.qty, 0);
  return <span>{formatMoney(total)}</span>;
}

// 糟糕的做法:將推導出的狀態另外儲存
function Cart({ items }: { items: CartItem[] }) {
  const [total, setTotal] = useState(0);
  useEffect(() => {
    setTotal(items.reduce((sum, i) => sum + i.price * i.qty, 0));
  }, [items]);
  return <span>{formatMoney(total)}</span>;
}

在 useEffect 中處理推導狀態會增加一次渲染循環,可能導致狀態不同步,並使資料流變得模糊不清。

2. 副作用在渲染體外部處理

副作用、突變、網路呼叫和訂閱都應該放在事件處理函式或 useEffect 中 — 絕對不要在渲染體中處理。

3. 組合優於繼承

React 沒有元件的繼承模型。請使用 children、render props 或元件 props 來進行組合。

Hooks 規範

請參閱 rules/react/hooks.md 以獲得完整的規則集。重點提示:

  • 只能在頂層呼叫,不可條件式呼叫
  • 每個訂閱、間隔器、監聽器都需要清理
  • 當新狀態依賴於舊狀態時,使用函數更新器 (setX(prev => prev + 1))
  • 預設立場:不要進行記憶化 — 只有在效能分析器或依賴鏈證明有必要時,才加入 useMemo/useCallback
  • 僅當同一組 Hook 序列出現在 2 個以上的元件時,才提取出自訂 Hook

狀態位置決策樹

只被一個元件使用?
  -> 在該元件內使用 useState

被父元件和少數子孫元件使用?
  -> 提升到最近的共同祖先元件

被分散的樹狀分支使用,且讀取頻率低(主題、驗證、語言環境)?
  -> React Context

在整個樹狀結構中高頻率更新?
  -> 外部 Store(Zustand、Jotai、Redux Toolkit)

從伺服器端推導?
  -> 伺服器狀態函式庫(TanStack Query、SWR、RSC fetch)

大多數頁面並不需要 Context 或全域 Store。在重複的狀態提升操作變得令人痛苦之前,請抵制住進行抽象化的衝動。

伺服器 / 客戶端元件 (RSC)

// 伺服器元件 - 預設,async,本身不會傳送 JS 給客戶端
export default async function ProductPage({ params }: { params: { id: string } }) {
  const product = await db.product.findUnique({ where: { id: params.id } });
  if (!product) notFound();
  return <ProductView product={product} />;
}

// 客戶端元件 - 使用 "use client" 選擇加入
"use client";
export function AddToCartButton({ productId }: { productId: string }) {
  const [pending, startTransition] = useTransition();
  return (
    <button
      disabled={pending}
      onClick={() => startTransition(() => addToCart(productId))}
    >
      {pending ? "新增中..." : "加入購物車"}
    </button>
  );
}

邊界:

  • 伺服器 -> 客戶端:傳遞可序列化的 props 或 children
  • 客戶端 -> 伺服器:透過 <form action={...}> 或從事件處理函式中命令式地呼叫伺服器動作
  • 絕對不要在客戶端元件檔案中 import 伺服器元件 — 透過 children 來組合它們

Suspense + Error Boundaries

<ErrorBoundary fallback={<ErrorView />}>
  <Suspense fallback={<UserSkeleton />}>
    <UserDetail id={id} />
  </Suspense>
</ErrorBoundary>
  • 將 Suspense 邊界放在靠近資料的地方,而不是路由根節點 — 逐步揭露內容
  • Error Boundary 仍然是類別 API;對於對 Hook 友善的封裝,請使用 react-error-boundary
  • Error Boundary 會捕捉在其子元件的渲染、生命週期和建構子期間拋出的錯誤 — 不會捕捉事件處理函式或非同步程式碼中的錯誤

表單

React 19 表單動作(新程式碼的首選)

"use client";
import { useActionState } from "react";

const initial = { error: null as string | null };

async function updateUserAction(_prev: typeof initial, formData: FormData) {
  "use server";
  const parsed = UserSchema.safeParse(Object.fromEntries(formData));
  if (!parsed.success) return { error: "無效的輸入" };
  await db.user.update({ where: { id: parsed.data.id }, data: parsed.data });
  return { error: null };
}

export function UserForm() {
  const [state, formAction, pending] = useActionState(updateUserAction, initial);
  return (
    <form action={formAction}>
      <input name="name" required />
      <button type="submit" disabled={pending}>儲存</button>
      {state.error && <p role="alert">{state.error}</p>}
    </form>
  );
}

受控輸入

當輸入值的變更需要驅動其他 UI、在每次按鍵時進行格式化,或實作即時驗證時,請使用受控輸入。

複雜表單

對於多步驟表單、動態欄位陣列或跨欄位驗證:請使用函式庫(React Hook Form、TanStack Form)。對於超出簡單複雜度的表單,自行實作狀態管理是一個維護上的陷阱。

資料獲取決策矩陣

需求工具
Next.js App Router 中每次請求的資料RSC await fetch()
客戶端快取 + 突變 + 失效TanStack Query
輕量級客戶端快取 + 重新驗證SWR
即時訂閱Server-Sent Events、WebSockets 或函式庫的訂閱 API
一次性的發送後忘記事件處理函式中的 fetch()

避免使用 useEffect + fetch 來處理應用程式資料 — 會導致競爭條件、無快取、無重試、無 Suspense 整合。

組合食譜

透過 children 的插槽

<Layout>
  <Header />
  <Main>{content}</Main>
</Layout>

具名插槽

<Page header={<Nav />} sidebar={<Filters />}>
  <Results />
</Page>

複合元件(透過 Context 共享狀態)

<Tabs defaultValue="profile">
  <Tabs.List>
    <Tabs.Trigger value="profile">個人資料</Tabs.Trigger>
    <Tabs.Trigger value="settings">設定</Tabs.Trigger>
  </Tabs.List>
  <Tabs.Panel value="profile"><Profile /></Tabs.Panel>
  <Tabs.Panel value="settings"><Settings /></Tabs.Panel>
</Tabs>

Render Prop / Function-as-Child

當父元件需要傳遞參數給渲染結果時很有用:

<DataLoader id={id}>
  {({ data, isLoading }) => isLoading ? <Spinner /> : <UserCard user={data} />}
</DataLoader>

現代的替代方案是:一個回傳相同形狀的 Hook (useData(id)) — 通常更簡潔。

效能

何時 React.memo 真正有幫助

僅在以下情況才將元件包裝在 React.memo 中:

  1. 該元件頻繁重新渲染
  2. 其 props 在多次渲染之間通常保持不變
  3. 其渲染成本可被量測到是昂貴的

React.memo 在每次渲染時都會增加一個相等性檢查。如果 props 在大多數渲染時都不同,那麼這個檢查就是純粹的開銷。

避免渲染串聯

  • 盡可能將狀態下推而不是上提
  • 拆分 Context:每個關注點一個 Context,這樣 themeContext 的變更就不會重新渲染 auth 的消費者
  • 對於外部狀態函式庫,使用 useSyncExternalStore — 對於安全的並行渲染是必需的

列表

  • 提供穩定的 key prop(資料庫 ID,而不是陣列索引)
  • 當可見項目數量超過約 50 且行內容不簡單時,使用 @tanstack/react-virtual 或 react-window 對長列表進行虛擬化

無障礙優先的組合

  • 在求助於 role 屬性之前,始終先渲染語義化的 HTML(<button>、<a>、<nav>、<main>)
  • 每個可互動元素都必須可以透過鍵盤到達
  • 表單輸入需要標籤 — 使用 <label htmlFor>,如果視覺上由圖示標記,則使用 aria-label
  • 在路由變更和模態框開啟/關閉時管理焦點
  • 在元件測試中執行 axe(請參閱 skills/react-testing)
  • 交叉連結:skills/accessibility/SKILL.md 涵蓋了 WCAG 標準和模式庫

路由

這項技能與路由無關。上述模式適用於 React Router、TanStack Router、Next.js App Router、Remix Router。路由特定的模式(載入器、動作、巢狀佈局)請遵循路由器的文件 — 這些是在 React 核心之上層層疊加的框架關注點。

範圍外(指向章節)

  • Next.js 細節:App Router 資料載入、Route Handlers、Middleware、Parallel Routes — 這是另一個關注點,請使用 Next.js 文件
  • React Native:平台特定的模式差異很大,需要一個獨立的 react-native-patterns 技能(目前不存在)
  • Remix:載入器/動作的慣例與 RSC 重疊,但請遵循 Remix 文件

相關資源

  • 規則:rules/react/ — 編碼風格、hooks、模式、安全性、測試
  • 技能:react-performance 包含了 Vercel 衍生的效能規則集、frontend-patterns 處理跨框架的 UI 關注點、accessibility、angular-developer 用於框架比較
  • 代理人:react-reviewer 用於程式碼審查、react-build-resolver 用於建置/打包錯誤
  • 指令:/react-review、/react-build、/react-test

範例

用於防抖搜尋的自訂 Hook

function useDebounce<T>(value: T, delay = 300): T {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const id = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(id);
  }, [value, delay]);
  return debounced;
}

function SearchBox() {
  const [query, setQuery] = useState("");
  const debounced = useDebounce(query, 300);
  const { data } = useQuery({
    queryKey: ["search", debounced],
    queryFn: () => searchApi(debounced),
    enabled: debounced.length > 0,
  });
  return (
    <>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      <Results items={data ?? []} />
    </>
  );
}

使用 React 19 useOptimistic 的樂觀 UI

"use client";
import { useOptimistic } from "react";

export function MessageList({ messages }: { messages: Message[] }) {
  const [optimistic, addOptimistic] = useOptimistic(
    messages,
    (state, newMessage: Message) => [...state, newMessage],
  );

  async function send(formData: FormData) {
    const text = String(formData.get("text"));
    addOptimistic({ id: "pending", text, sender: "me" });
    await saveMessage(text);
  }

  return (
    <>
      <ul>{optimistic.map((m) => <li key={m.id}>{m.text}</li>)}</ul>
      <form action={send}>
        <input name="text" />
        <button type="submit">傳送</button>
      </form>
    </>
  );
}

拆分 Context 以避免渲染串聯

// 兩個 Context:一個很少變更,一個頻繁變更
const ThemeContext = createContext<Theme>("light");
const NotificationsContext = createContext<Notification[]>([]);

// 一個只使用 ThemeContext 的元件在通知變更時不會重新渲染

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.

相關技能