Skip to main content

react-patterns

React 18/19 模式,包括 hooks 纪律、服务端/客户端组件边界、Suspense + 错误边界、表单 actions、数据获取、状态管理决策树和可访问性优先组合。编写或审查 React 组件时使用。

Quellinformationen

Repository
aaione/everything-claude-code-zh
Letzte Quellaktivität
31. Mai 2026 um 16:26
Erkannte Sprache von SKILL.md
Englisch
Sterne
30
Forks
14

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
react-patterns
description
React 18/19 模式,包括 hooks 纪律、服务端/客户端组件边界、Suspense + 错误边界、表单 actions、数据获取、状态管理决策树和可访问性优先组合。编写或审查 React 组件时使用。
origin
ECC
# React 模式 Idiomatic React 18/19 patterns for building robust, accessible, performant component trees. ## 何时激活 - Writing or modifying React function components, custom hooks, or component trees - Reviewing JSX/TSX files - Designing state shape or component composition - Migrating class components or older `forwardRef`/`useEffect`-heavy code - Choosing between local state, lifted state, context, and external stores - Working with Server Components / Client Components (Next.js App Router, RSC) - Implementing forms with React 19 actions or controlled inputs - Wiring data fetching with TanStack Query / SWR / RSC ## 核心原则 ### 1. Render is a Pure Function of Props and State ```tsx // Good: derive during render function Cart({ items }: { items: CartItem[] }) { const total = items.reduce((sum, i) => sum + i.price * i.qty, 0); return <span>{formatMoney(total)}</span>; } // Bad: derived state stored separately 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>; } ``` Derived state in `useEffect` adds a render cycle, can desync, and obscures the data flow. ### 2. Side Effects Outside Render Effects, mutations, network calls, and subscriptions live in event handlers or `useEffect` — never in the render body. ### 3. Composition Over Inheritance React has no inheritance model for components. Compose with `children`, render props, or component props. ## Hooks 纪律 See [rules/react/hooks.md](../../rules/react/hooks.md) for the full ruleset. Highlights: - Top-level only, never conditional - Cleanup every subscription, interval, listener - Functional updater (`setX(prev => prev + 1)`) when new state depends on old - Default position: do not memoize — add `useMemo`/`useCallback` only when a profiler or a dependency chain proves it matters - Extract a custom hook only when the same hook sequence appears in 2+ components ## 状态位置决策树 ``` Used by one component? -> useState inside it Used by parent + a few descendants? -> lift to nearest common ancestor Used across distant branches AND low-frequency reads (theme, auth, locale)? -> React Context High-frequency updates shared across the tree? -> external store (Zustand, Jotai, Redux Toolkit) Derived from a server? -> server-state library (TanStack Query, SWR, RSC fetch) ``` Most pages do not need context or a global store. Resist abstraction until duplicated lifting becomes painful. ## 服务端/客户端组件(RSC) ```tsx // Server Component - default, async, never ships JS for itself 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} />; } // Client Component - opt in with "use client" "use client"; export function AddToCartButton({ productId }: { productId: string }) { const [pending, startTransition] = useTransition(); return ( <button disabled={pending} onClick={() => startTransition(() => addToCart(productId))} > {pending ? "Adding..." : "Add to cart"} </button> ); } ``` Boundaries: - Server -> Client: pass serializable props or `children` - Client -> Server: invoke Server Actions via `<form action={...}>` or imperatively from event handlers - Never `import` a Server Component from a Client Component file — compose them via `children` instead ## Suspense + 错误边界 ```tsx <ErrorBoundary fallback={<ErrorView />}> <Suspense fallback={<UserSkeleton />}> <UserDetail id={id} /> </Suspense> </ErrorBoundary> ``` - Place Suspense boundaries close to the data, not at the route root — progressively reveal content - Error Boundary remains a class API; use `react-error-boundary` for a hook-friendly wrapper - A boundary catches errors thrown during render, lifecycle, and constructors of its children — NOT in event handlers or async code ## 表单 ### React 19 表单 actions(新代码推荐) ```tsx "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: "Invalid input" }; 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}>Save</button> {state.error && <p role="alert">{state.error}</p>} </form> ); } ``` ### 受控输入 Use controlled when the value drives other UI, formats on every keystroke, or implements real-time validation. ### 复杂表单 For multi-step forms, dynamic field arrays, or cross-field validation: use a library (React Hook Form, TanStack Form). Roll-your-own state management for forms past trivial complexity is a maintenance trap. ## 数据获取决策矩阵 | Need | Tool | |---|---| | Per-request data in Next.js App Router | RSC `await fetch()` | | Client-side cache + mutations + invalidation | TanStack Query | | Lightweight client cache + revalidation | SWR | | Real-time subscriptions | Server-Sent Events, WebSockets, or the lib's subscription API | | One-off fire-and-forget | `fetch()` in an event handler | Avoid `useEffect` + `fetch` for application data — race conditions, no cache, no retry, no Suspense integration. ## 组合模式 ### 通过 `children` 实现插槽 ```tsx <Layout> <Header /> <Main>{content}</Main> </Layout> ``` ### 命名插槽 ```tsx <Page header={<Nav />} sidebar={<Filters />}> <Results /> </Page> ``` ### 复合组件(通过 Context 共享状态) ```tsx <Tabs defaultValue="profile"> <Tabs.List> <Tabs.Trigger value="profile">Profile</Tabs.Trigger> <Tabs.Trigger value="settings">Settings</Tabs.Trigger> </Tabs.List> <Tabs.Panel value="profile"><Profile /></Tabs.Panel> <Tabs.Panel value="settings"><Settings /></Tabs.Panel> </Tabs> ``` ### 渲染属性 / 函数作为子组件 Useful when the parent needs to pass parameters to the rendered output: ```tsx <DataLoader id={id}> {({ data, isLoading }) => isLoading ? <Spinner /> : <UserCard user={data} />} </DataLoader> ``` Modern alternative: a hook (`useData(id)`) returning the same shape — usually cleaner. ## 性能 ### 何时 `React.memo` 真正有用 Wrap a component in `React.memo` only when: 1. It re-renders frequently 2. Its props are usually the same between renders 3. Its render is measurably expensive `React.memo` adds an equality check on every render. If props differ on most renders, the check is pure overhead. ### 避免渲染级联 - Lift state down rather than up where possible - Split context: one context per concern, so a change to `themeContext` does not re-render auth consumers - Use `useSyncExternalStore` for external state libraries — required for safe concurrent rendering ### 列表 - Provide stable `key` props (database id, not array index) - Virtualize long lists with `@tanstack/react-virtual` or `react-window` once visible item count exceeds ~50 with non-trivial rows ## 可访问性-First Composition - Always render semantic HTML (`<button>`, `<a>`, `<nav>`, `<main>`) before reaching for `role` attributes - Every interactive element must be reachable by keyboard - Form inputs need labels — `<label htmlFor>` or `aria-label` if visually labeled by an icon - Manage focus on route changes and modal open/close - Run `axe` in component tests (see [skills/react-testing](../react-testing/SKILL.md)) - Cross-link: [skills/accessibility/SKILL.md](../accessibility/SKILL.md) covers WCAG criteria and pattern libraries ## 路由 This skill is router-agnostic. The patterns above work with React Router, TanStack Router, Next.js App Router, Remix Router. Router-specific patterns (loaders, actions, nested layouts) follow the router's documentation — those are framework concerns layered on top of React core. ## 超出范围(指针章节) - **Next.js specifics**: App Router data loading, Route Handlers, Middleware, Parallel Routes — separate concern, use Next.js docs - **React Native**: Platform-specific patterns differ enough to warrant a separate `react-native-patterns` skill (not present yet) - **Remix**: Loader/action conventions overlap with RSC but follow Remix docs ## 相关内容 - Rules: [rules/react/](../../rules/react/) — coding-style, hooks, patterns, security, testing - Skills: [react-performance](../react-performance/SKILL.md) for the Vercel-derived performance ruleset, [frontend-patterns](../frontend-patterns/SKILL.md) for cross-framework UI concerns, [accessibility](../accessibility/SKILL.md), [angular-developer](../angular-developer/SKILL.md) for framework comparison - Agents: `react-reviewer` for code review, `react-build-resolver` for build/bundler errors - Commands: `/react-review`, `/react-build`, `/react-test` ## 示例 ### 防抖搜索自定义 Hook ```tsx 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 ```tsx "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">Send</button> </form> </> ); } ``` ### 拆分上下文以避免渲染级联 ```tsx // Two contexts: one rarely changes, one frequently const ThemeContext = createContext<Theme>("light"); const NotificationsContext = createContext<Notification[]>([]); // A component that only consumes ThemeContext does NOT re-render when notifications change ```
Auf GitHub ansehen