# React

> React 19 最佳實踐指南。當需要撰寫 React 元件、使用 Hooks、處理狀態管理、或搭配 TypeScript/Vite 開發時使用。

- Skill: `recca0120/react` (Agent Skill)
- Install (CLI): `npx skillmds@latest add recca0120/react`
- Raw SKILL.md: https://api.skillmd.com/api/skills/recca0120/react/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: recca0120 (https://skillmd.com/u/recca0120)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/recca0120/react

---


# React 19 最佳實踐

> 基於 2025/5 知識。React 19 已 stable，React Compiler 進入推薦使用。

## TypeScript 寫法

```tsx
// 不要用 React.FC（不支援 generics、implicit children）
function Button({ label, onClick }: { label: string; onClick: () => void }) {
  return <button onClick={onClick}>{label}</button>
}

// children 明確宣告
type CardProps = { title: string; children: React.ReactNode }

// 抽 props 用 ComponentProps
type InputProps = React.ComponentProps<'input'> & { label: string }
```

## React 19 新特性重點

### 1. ref 可當 prop（不再需要 forwardRef）

```tsx
function MyInput({ ref, ...props }: { ref?: React.Ref<HTMLInputElement> } & React.ComponentProps<'input'>) {
  return <input ref={ref} {...props} />
}
```

### 2. Actions + useActionState

```tsx
const [state, formAction, isPending] = useActionState(async (prev, formData) => {
  const res = await submit(formData)
  return res.error ?? null
}, null)

<form action={formAction}>...</form>
```

### 3. useOptimistic

立刻顯示樂觀更新，等 server 回來時 reconcile。

### 4. use() Hook

可在 render 中 unwrap Promise 或 Context（Suspense 邊界接）：
```tsx
const data = use(fetchPromise) // 搭配 <Suspense>
const theme = use(ThemeContext)
```

### 5. `<Context>` 直接當 Provider

```tsx
<ThemeContext value={theme}> {/* 不必 .Provider */}
```

### 6. Document Metadata

可以直接在元件內寫 `<title>`, `<meta>`, `<link>`，React 會 hoist 到 head。

## Hooks 守則

### useEffect 反模式
- ❌ 用 effect 同步兩個 state → 直接計算 derived value
- ❌ 用 effect 處理 user event → 寫在 event handler
- ❌ 用 effect fetch → 用 Suspense + use() 或 TanStack Query
- ✅ 真正需要：訂閱外部系統、DOM measure、整合非 React 函式庫

### useMemo / useCallback
不要預防性加。React Compiler 啟用後幾乎都不需要。手動加只在：
- 昂貴計算（實測 >1ms）
- 傳給 memo 化子元件的 prop/callback
- 當依賴陣列用

### StrictMode 雙重執行
開發模式下 effect/state initializer 會跑兩次，是為了暴露不純副作用。cleanup 要寫對才不會 leak。

## React Compiler（推薦啟用）

```bash
pnpm add -D babel-plugin-react-compiler
```

`vite.config.ts`：
```ts
react({ babel: { plugins: ['babel-plugin-react-compiler'] } })
```

啟用後可刪掉大部分 memo/useMemo/useCallback。

## 常見陷阱

1. **list key 用 index**：reorder/insert 會壞，用穩定 ID。
2. **state 放物件忘記 immutable update**：`setX({ ...x, a: 1 })` 不是 `x.a = 1`。
3. **async 在 effect**：effect 函式本身不能 async，內層宣告 `async function` 再呼叫。
4. **Context 每次 render 新物件**：value 用 useMemo 或拆多個 Context。
5. **Portal 不走事件冒泡的 DOM 樹**：但走 React tree（所以 Context 可透）。

