狀態管理:為什麼需要 Redux/Pinia?
問題:Props Drilling
App
├── Header
│ └── UserAvatar ← 需要 user
├── Sidebar
│ └── CartCount ← 需要 cart
└── Main
└── ProductList
└── ProductCard
└── AddToCartButton ← 需要 cart + user
user 資料在 App 層,要傳到 UserAvatar 要穿過 Header
cart 資料要從 App → Main → ProductList → ProductCard → AddToCartButton
→ 中間的元件根本不需要這些資料,但被迫要傳遞
→ 這就是 "Props Drilling"(Props 穿透)
解法 1:Context API(React)/ Provide-Inject(Vue)
// React Context
const UserContext = createContext();
function App() {
return (
<UserContext.Provider value={user}>
<Header /> {/* 不需要傳 props */}
</UserContext.Provider>
);
}
function UserAvatar() {
const user = useContext(UserContext); // 直接取
}
優點:內建、不需要額外套件
缺點:
- 值改變時,所有用到 Context 的元件都會重新渲染
- 不適合頻繁變化的資料
- 沒有 devtools、中間件
解法 2:狀態管理庫(Redux / Pinia / Zustand)
核心概念:
┌──────────────────┐
│ Store │ ← 全域狀態(Single Source of Truth)
│ ┌────────────┐ │
│ │ state │ │ ← 資料
│ │ getters │ │ ← 計算屬性(derived state)
│ │ actions │ │ ← 改變狀態的方法
│ └────────────┘ │
└──────────────────┘
↕
任何元件都能直接存取
什麼時候需要?什麼時候不需要?
| 需要 | 不需要 |
|---|---|
| 多個不相關的元件共享資料 | 父子元件間傳遞(用 props) |
| 使用者登入狀態 | 表單的臨時輸入值 |
| 購物車 | Modal 的開關狀態 |
| 複雜的資料同步邏輯 | 簡單的 CRUD 頁面 |
Dan Abramov(Redux 作者):"如果你不確定需不需要 Redux,那你不需要。"
現代趨勢
2016:Redux 一統天下(嚴格的 action + reducer)
2020:Redux Toolkit 簡化(減少 boilerplate)
2022:Zustand / Jotai / Pinia 崛起(更簡單、更少概念)
2024:Server Components(React)把狀態移回伺服器
趨勢:越來越簡單、越來越少全域狀態
很多「需要」全域狀態的場景,其實用 URL 參數、Server State(React Query / SWR)就能解決
能用 props 解決就用 props,能用 Context 就用 Context,真的需要時才上狀態管理庫。