技術 6 分鐘閱讀

React 狀態管理方案比較:該選哪一個?

React 狀態管理方案比較:該選哪一個?

前言

「React 的狀態管理該用什麼?」這是我最常被問到的問題之一。選擇太多反而讓人困惑,這篇文章幫你整理各個方案的優缺點,讓你做出最適合的選擇。

先搞清楚:你真的需要狀態管理嗎?

在決定用什麼工具之前,先問自己:

  • 狀態是否需要跨多個元件共享?
  • useState 和 props drilling 是否已經夠用?
  • 狀態的複雜度真的需要外部工具嗎?

很多中小型專案其實用 React 內建的功能就夠了。不要為了用工具而用工具。

方案一:Context API + useReducer

適合場景

  • 中小型應用
  • 不常變動的全域狀態(主題、語言、使用者資訊)
  • 不想引入額外套件

實作範例

// contexts/AuthContext.tsx
interface AuthState {
  user: User | null;
  isLoading: boolean;
}

type AuthAction =
  | { type: 'LOGIN'; payload: User }
  | { type: 'LOGOUT' }
  | { type: 'SET_LOADING'; payload: boolean };

const authReducer = (state: AuthState, action: AuthAction): AuthState => {
  switch (action.type) {
    case 'LOGIN':
      return { ...state, user: action.payload, isLoading: false };
    case 'LOGOUT':
      return { ...state, user: null, isLoading: false };
    case 'SET_LOADING':
      return { ...state, isLoading: action.payload };
    default:
      return state;
  }
};

const AuthContext = createContext<{
  state: AuthState;
  dispatch: React.Dispatch<AuthAction>;
} | null>(null);

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [state, dispatch] = useReducer(authReducer, {
    user: null,
    isLoading: true,
  });

  return (
    <AuthContext.Provider value={{ state, dispatch }}>
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) throw new Error('useAuth must be used within AuthProvider');
  return context;
}

缺點

Context 的值一變動,所有消費者都會重新渲染,即使它只用了其中一部分的狀態。對於頻繁變動的狀態(如表單輸入),效能可能會有問題。

方案二:Zustand

適合場景

  • 中大型應用
  • 需要高效能的狀態更新
  • 喜歡簡潔的 API

為什麼我推薦 Zustand

Zustand 是我目前最推薦的狀態管理方案,原因如下:

  1. API 極簡:學習成本低
  2. 效能好:自動只重新渲染用到的元件
  3. 不需要 Provider:減少元件巢狀
  4. TypeScript 支援好:型別推導完整

實作範例

import { create } from 'zustand';

interface CartStore {
  items: CartItem[];
  addItem: (product: Product) => void;
  removeItem: (productId: number) => void;
  updateQuantity: (productId: number, quantity: number) => void;
  clearCart: () => void;
  totalPrice: () => number;
}

const useCartStore = create<CartStore>((set, get) => ({
  items: [],

  addItem: (product) =>
    set((state) => {
      const existing = state.items.find((i) => i.product.id === product.id);
      if (existing) {
        return {
          items: state.items.map((i) =>
            i.product.id === product.id
              ? { ...i, quantity: i.quantity + 1 }
              : i
          ),
        };
      }
      return { items: [...state.items, { product, quantity: 1 }] };
    }),

  removeItem: (productId) =>
    set((state) => ({
      items: state.items.filter((i) => i.product.id !== productId),
    })),

  updateQuantity: (productId, quantity) =>
    set((state) => ({
      items: state.items.map((i) =>
        i.product.id === productId ? { ...i, quantity } : i
      ),
    })),

  clearCart: () => set({ items: [] }),

  totalPrice: () =>
    get().items.reduce((sum, i) => sum + i.product.price * i.quantity, 0),
}));

在元件中使用:

function CartButton() {
  // 只訂閱 items.length,不會因為其他狀態變更而重新渲染
  const itemCount = useCartStore((state) => state.items.length);
  return <button>購物車 ({itemCount})</button>;
}

function CartTotal() {
  const totalPrice = useCartStore((state) => state.totalPrice());
  return <p>總計:NT$ {totalPrice.toLocaleString()}</p>;
}

方案三:Redux Toolkit

適合場景

  • 大型企業應用
  • 需要嚴格的狀態管理規範
  • 團隊已經熟悉 Redux 生態系

為什麼不再首推 Redux

Redux Toolkit 已經大幅簡化了 Redux 的使用方式,但對於大多數接案專案來說,它仍然太重了:

  • 需要理解 slice、action、reducer 的概念
  • 設定檔案比較多
  • 學習曲線比 Zustand 陡

如果不是大型專案或團隊沒有 Redux 經驗,我通常不會選擇它。

方案四:TanStack Query(React Query)

適合場景

  • 需要管理伺服器端狀態
  • 需要快取、自動重新取得、樂觀更新

要注意

TanStack Query 是管理伺服器端狀態的工具,不是一般的狀態管理工具。它處理的是 API 資料的快取和同步,跟 Zustand 或 Redux 是不同層級的東西。

實務上我常常 Zustand + TanStack Query 搭配使用:

  • Zustand:管理 UI 狀態(Modal 開關、篩選條件等)
  • TanStack Query:管理 API 資料(商品列表、使用者資料等)
// 用 TanStack Query 管理 API 資料
const { data: products, isLoading } = useQuery({
  queryKey: ['products', filters],
  queryFn: () => api.getProducts(filters),
});

// 用 Zustand 管理 UI 狀態
const filters = useFilterStore((state) => state.filters);
const setFilter = useFilterStore((state) => state.setFilter);

方案比較表

特性ContextZustandRedux ToolkitTanStack Query
學習成本
效能普通
套件大小01KB11KB13KB
DevTools
適合規模中大所有

我的建議

  1. 小型專案:Context API 就夠了
  2. 中型接案專案:Zustand + TanStack Query
  3. 大型企業專案:Redux Toolkit + TanStack Query
  4. 不確定的話:先用 Zustand,它的遷移成本最低

結語

狀態管理沒有銀彈,關鍵是選擇適合專案規模和團隊經驗的方案。我個人在接案中最常用的組合是 Zustand + TanStack Query,簡單好用又夠彈性。希望這篇比較能幫你做出更好的選擇。

#React #狀態管理 #Zustand #Redux
Thousand Lab

Thousand Lab