Communitygithub.com

react-patterns

React 18/19 模式:涵盖 Hooks 规范、服务端/客户端组件边界、Suspense + 错误边界、表单操作、数据获取、状态管理决策树以及无障碍优先的组合方式。在编写或审查 React 组件时使用。

react-patterns 是什么?

这是一个针对 React 18 和 19 的综合性技能指南,指导开发者如何构建健壮、可访问且高性能的组件树。它提供了关于核心原则(如渲染作为 props 和 state 的纯函数、副作用管理、组合优于继承)、Hooks 规范、状态位置决策树(useState vs 提升状态 vs Context vs 外部 store)、服务端/客户端组件边界、Suspense + 错误边界、表单操作(React 19 actions 和受控输入)以及数据获取决策矩阵的详细模式。该技能还涵盖了性能优化(React.memo、避免渲染瀑布流、列表虚拟化)、无障碍优先的组合、路由无关的模式、复合组件、插槽模式以及自定义 hooks 示例(如防抖搜索和乐观更新)。它支持各种平台(Next.js、React Router、TanStack Router 等),并指导开发者在复杂的表单、数据获取和状态管理中何时使用库而不是自己实现。

兼容平台✓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 函数组件、自定义 hooks 或组件树
  • 审查 JSX/TSX 文件
  • 设计状态形状或组件组合
  • 迁移类组件或旧的 forwardRef/useEffect 密集型代码
  • 在本地状态、提升状态、Context 和外部 store 之间进行选择
  • 使用服务端组件 / 客户端组件(Next.js App Router, RSC)
  • 使用 React 19 actions 或受控输入实现表单
  • 配合 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)

// 服务端组件 - 默认,异步,不为自身下发 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={...}> 或从事件处理函数中命令式地调用 Server Actions
  • 永远不要从客户端组件文件 import 服务端组件——改为通过 children 组合它们

Suspense + 错误边界

<ErrorBoundary fallback={<ErrorView />}>
  <Suspense fallback={<UserSkeleton />}>
    <UserDetail id={id} />
  </Suspense>
</ErrorBoundary>
  • 将 Suspense 边界放在靠近数据的地方,而不是路由根节点——逐步揭示内容
  • Error Boundary 仍然是类 API;使用 react-error-boundary 获得对 hooks 友好的包装器
  • 边界捕获其子组件在渲染、生命周期和构造函数期间抛出的错误——而不是在事件处理函数或异步代码中

表单

React 19 表单 actions(新代码首选)

"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 / 函数作为子组件

当父组件需要向渲染输出传递参数时很有用:

<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。特定于路由的模式(loaders、actions、嵌套布局)应遵循路由器的文档——这些是构建在 React 核心之上的框架关注点。

范围之外(指针部分)

  • Next.js 细节:App Router 数据加载、Route Handlers、Middleware、Parallel Routes——独立的关注点,请使用 Next.js 文档
  • React Native:平台特定模式差异很大,有理由建立一个独立的 react-native-patterns 技能(目前尚未存在)
  • Remix:Loader/action 约定与 RSC 重叠,但请遵循 Remix 文档

相关

  • 规则:rules/react/ — 编码风格、hooks、模式、安全、测试
  • 技能:react-performance 获取 Vercel 衍生的性能规则集,frontend-patterns 获取跨框架 UI 关注点,accessibility,angular-developer 用于框架比较
  • Agent: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.

相关技能