學習 · 前端框架
Redux
Redux 安裝與套件分工
TL;DR三層套件分工:redux(狀態容器本身)+ react-redux(綁進 React 的 hooks)+ @reduxjs/toolkit(現代寫法首選,大幅簡化 boilerplate)。2026 年新專案不該再寫純 redux,要用就用 RTK,或直接換 Zustand / Jotai。
安裝
# 現代寫法(必裝)
npm install @reduxjs/toolkit react-redux
npm install -D @types/react-redux
# 純 Redux(已不需要,RTK 已包含)
# npm install redux react-redux不要兩個都裝RTK 已內含 redux,別重複裝 redux。如果 package.json 只看到 RTK + react-redux,那是正確的。
Next.js 加裝
npm install next-redux-wrapper處理 SSR / SSG 的 store hydration,讓 server 跟 client 的 redux state 一致。App Router(Next.js 13+)做法不同,要 <StoreProvider> client component 包裹。
三個套件的分工
| 套件 | 角色 |
|---|---|
redux | 狀態容器本身,跟 React 沒關係,任何 JS 環境都能用 |
react-redux | Redux 跟 React 的橋梁,提供 <Provider> / useSelector / useDispatch |
@reduxjs/toolkit (RTK) | 官方推薦的現代 wrapper,自動 immer + boilerplate 減少 70% |
我只用 vanilla JS 不用 React?那就只裝 redux,不需要 react-redux。但 2026 年純 vanilla JS 的場景幾乎沒了,有就用 Zustand / Valtio / Pinia 都比 Redux 輕。
經典三段式 Redux(舊寫法,不推薦)
// action
const INCREMENT = 'INCREMENT';
const increment = () => ({ type: INCREMENT });
// reducer
function counter(state = 0, action) {
switch (action.type) {
case INCREMENT: return state + 1;
default: return state;
}
}
// store
import { createStore } from 'redux';
const store = createStore(counter);寫起來超繁瑣,改個 state 要寫 3 個地方。所以 RTK 出現了。
現代寫法(RTK)
// counterSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: 0,
reducers: {
increment: (state) => state + 1, // 不用 return!immer 處理
incrementByAmount: (state, action: PayloadAction<number>) => {
return state + action.payload;
},
},
});
export const { increment, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;// store.ts
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;// App.tsx
import { Provider } from 'react-redux';
import { store } from './store';
<Provider store={store}>
<App />
</Provider>// 元件內使用
import { useSelector, useDispatch } from 'react-redux';
import { increment } from './counterSlice';
function Counter() {
const count = useSelector((state: RootState) => state.counter);
const dispatch = useDispatch<AppDispatch>();
return <button onClick={() => dispatch(increment())}>{count}</button>;
}RTK 內建 immer,你可以「直接 mutate」state,immer 會自動處理 immutability。
異步處理:thunk vs saga
| 套件 | 學習曲線 | 適合 |
|---|---|---|
redux-thunk | ⭐ 簡單 | 一般 API call、簡單 async flow(RTK 內建,不用額外裝) |
redux-saga | ⭐⭐⭐ 陡 | 複雜的 async flow(取消、retry、長運行 task) |
# saga 才需要安裝;thunk 已在 RTK 內
npm install redux-saga使用 RTK Query(取代 thunk + 自己寫 fetch)
RTK Query 是 2026 年的首選,取代 SWR / TanStack Query 在 Redux 生態的位置:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const userApi = createApi({
reducerPath: 'userApi',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: (builder) => ({
getUserById: builder.query<User, string>({
query: (id) => `users/${id}`,
}),
createUser: builder.mutation<User, NewUser>({
query: (user) => ({ url: 'users', method: 'POST', body: user }),
}),
}),
});
export const { useGetUserByIdQuery, useCreateUserMutation } = userApi;自動 cache、loading state、refetch、optimistic update。比手寫 thunk 省 80%。
2026 年:Redux 還值得用嗎?
| 場景 | 推薦 |
|---|---|
| 新專案 + 簡單 state(主題、語言) | Context API |
| 新專案 + 中等規模 | Zustand / Jotai |
| 新專案 + 大規模 + 嚴格規範 | RTK + RTK Query |
| 接手既有 Redux 專案 | RTK migration |
| vanilla JS | Zustand vanilla / Valtio |
個人建議中小型 React 專案 → Zustand(全部 store 一個檔、學習半小時、bundle <1KB)。大型 / 跨團隊 / 強約束 → RTK。純 Redux(不含 RTK)已沒人寫。
// Zustand 對比範例(50 行 → 5 行)
import { create } from 'zustand';
const useCounter = create<{ count: number; increment: () => void }>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));
// 使用:
const { count, increment } = useCounter();