Skill 寫法圖鑑

react-state-management

wshobson | 架構與重構 | 原文 | MIT

在 Redux Toolkit、Zustand、Jotai 和 React Query 之間選擇並用好

中文翻譯

---
name: react-state-management
description: 精通現代 React 狀態管理,包含 Redux Toolkit、Zustand、Jotai 和 React Query。在設定全域狀態、管理伺服器狀態,或在各種狀態管理方案之間做選擇時使用。
---

# React State Management

一份現代 React 狀態管理模式的完整指南,從元件本地狀態、全域 store,到伺服器狀態同步。

## When to Use This Skill

- 在 React 應用裡設定全域狀態管理
- 在 Redux Toolkit、Zustand 或 Jotai 之間做選擇
- 用 React Query 或 SWR 管理伺服器狀態
- 實作樂觀更新(optimistic update)
- 除錯跟狀態有關的問題
- 從舊版 Redux 遷移到現代寫法

## Core Concepts

### 1. State Categories

| Type             | Description                  | Solutions                     |
| ---------------- | ---------------------------- | ----------------------------- |
| **Local State**  | 元件專屬的 UI 狀態 | useState, useReducer          |
| **Global State** | 跨元件共用     | Redux Toolkit, Zustand, Jotai |
| **Server State** | 遠端資料、快取         | React Query, SWR, RTK Query   |
| **URL State**    | 路由參數、搜尋     | React Router, nuqs            |
| **Form State**   | 輸入值、驗證     | React Hook Form, Formik       |

### 2. Selection Criteria

```
Small app, simple state → Zustand or Jotai
Large app, complex state → Redux Toolkit
Heavy server interaction → React Query + light client state
Atomic/granular updates → Jotai
```

## Quick Start

### Zustand (Simplest)

```typescript
// store/useStore.ts
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'

interface AppState {
  user: User | null
  theme: 'light' | 'dark'
  setUser: (user: User | null) => void
  toggleTheme: () => void
}

export const useStore = create<AppState>()(
  devtools(
    persist(
      (set) => ({
        user: null,
        theme: 'light',
        setUser: (user) => set({ user }),
        toggleTheme: () => set((state) => ({
          theme: state.theme === 'light' ? 'dark' : 'light'
        })),
      }),
      { name: 'app-storage' }
    )
  )
)

// Usage in component
function Header() {
  const { user, theme, toggleTheme } = useStore()
  return (
    <header className={theme}>
      {user?.name}
      <button onClick={toggleTheme}>Toggle Theme</button>
    </header>
  )
}
```

## Detailed patterns and worked examples

詳細的 pattern 文件放在 `references/details.md`。上面這層導覽不夠用的時候,再去讀那個檔案。

## Best Practices

### Do's

- **狀態就近放(colocate)** - 狀態盡量放在最靠近使用它的地方
- **用 selector** - 用選擇性訂閱避免不必要的 re-render
- **資料正規化** - 把巢狀結構攤平,更新起來比較容易
- **全部都加型別** - 完整的 TypeScript 涵蓋可以預防 runtime 錯誤
- **關注點分離** - 伺服器狀態(React Query)和 client 狀態(Zustand)分開

### Don'ts

- **不要過度全域化** - 不是所有東西都要放進全域狀態
- **不要重複存伺服器狀態** - 交給 React Query 管
- **不要直接 mutate** - 一律用 immutable 的方式更新
- **不要存衍生資料** - 要用的時候再算
- **不要混用不同典範** - 每個類別選一個主要方案

## Migration Guides

### From Legacy Redux to RTK

```typescript
// Before (legacy Redux)
const ADD_TODO = "ADD_TODO";
const addTodo = (text) => ({ type: ADD_TODO, payload: text });
function todosReducer(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [...state, { text: action.payload, completed: false }];
    default:
      return state;
  }
}

// After (Redux Toolkit)
const todosSlice = createSlice({
  name: "todos",
  initialState: [],
  reducers: {
    addTodo: (state, action: PayloadAction<string>) => {
      // Immer allows "mutations"
      state.push({ text: action.payload, completed: false });
    },
  },
});
```

英文原文

---
name: react-state-management
description: Master modern React state management with Redux Toolkit, Zustand, Jotai, and React Query. Use when setting up global state, managing server state, or choosing between state management solutions.
---

# React State Management

Comprehensive guide to modern React state management patterns, from local component state to global stores and server state synchronization.

## When to Use This Skill

- Setting up global state management in a React app
- Choosing between Redux Toolkit, Zustand, or Jotai
- Managing server state with React Query or SWR
- Implementing optimistic updates
- Debugging state-related issues
- Migrating from legacy Redux to modern patterns

## Core Concepts

### 1. State Categories

| Type             | Description                  | Solutions                     |
| ---------------- | ---------------------------- | ----------------------------- |
| **Local State**  | Component-specific, UI state | useState, useReducer          |
| **Global State** | Shared across components     | Redux Toolkit, Zustand, Jotai |
| **Server State** | Remote data, caching         | React Query, SWR, RTK Query   |
| **URL State**    | Route parameters, search     | React Router, nuqs            |
| **Form State**   | Input values, validation     | React Hook Form, Formik       |

### 2. Selection Criteria

```
Small app, simple state → Zustand or Jotai
Large app, complex state → Redux Toolkit
Heavy server interaction → React Query + light client state
Atomic/granular updates → Jotai
```

## Quick Start

### Zustand (Simplest)

```typescript
// store/useStore.ts
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'

interface AppState {
  user: User | null
  theme: 'light' | 'dark'
  setUser: (user: User | null) => void
  toggleTheme: () => void
}

export const useStore = create<AppState>()(
  devtools(
    persist(
      (set) => ({
        user: null,
        theme: 'light',
        setUser: (user) => set({ user }),
        toggleTheme: () => set((state) => ({
          theme: state.theme === 'light' ? 'dark' : 'light'
        })),
      }),
      { name: 'app-storage' }
    )
  )
)

// Usage in component
function Header() {
  const { user, theme, toggleTheme } = useStore()
  return (
    <header className={theme}>
      {user?.name}
      <button onClick={toggleTheme}>Toggle Theme</button>
    </header>
  )
}
```

## Detailed patterns and worked examples

Detailed pattern documentation lives in `references/details.md`. Read that file when the navigation tier above is insufficient.

## Best Practices

### Do's

- **Colocate state** - Keep state as close to where it's used as possible
- **Use selectors** - Prevent unnecessary re-renders with selective subscriptions
- **Normalize data** - Flatten nested structures for easier updates
- **Type everything** - Full TypeScript coverage prevents runtime errors
- **Separate concerns** - Server state (React Query) vs client state (Zustand)

### Don'ts

- **Don't over-globalize** - Not everything needs to be in global state
- **Don't duplicate server state** - Let React Query manage it
- **Don't mutate directly** - Always use immutable updates
- **Don't store derived data** - Compute it instead
- **Don't mix paradigms** - Pick one primary solution per category

## Migration Guides

### From Legacy Redux to RTK

```typescript
// Before (legacy Redux)
const ADD_TODO = "ADD_TODO";
const addTodo = (text) => ({ type: ADD_TODO, payload: text });
function todosReducer(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [...state, { text: action.payload, completed: false }];
    default:
      return state;
  }
}

// After (Redux Toolkit)
const todosSlice = createSlice({
  name: "todos",
  initialState: [],
  reducers: {
    addTodo: (state, action: PayloadAction<string>) => {
      // Immer allows "mutations"
      state.push({ text: action.payload, completed: false });
    },
  },
});
```