什麼是 Redux Toolkit (RTK)?
Redux Toolkit 是 Redux 官方推薦的工具集,旨在簡化 Redux 的使用,讓狀態管理變得更直觀、更高效。它解決了傳統 Redux 的幾個常見問題,例如:
-
樣板程式碼繁瑣:傳統 Redux 需要撰寫大量的 action creators、reducer 程式碼,RTK 大幅簡化這些流程。
-
設定複雜:RTK 提供預設的最佳實踐,減少手動配置。
-
學習曲線陡峭:RTK 的 API 設計更友善,適合新手快速上手。
簡單來說,RTK 是 Redux 的「升級版」,讓你用更少的程式碼實現相同的狀態管理功能。它特別適合用於管理 React 應用程式的全局狀態(例如使用者資料、購物車內容等)。
為什麼需要 Redux Toolkit?
在 React 中,狀態管理工具可以幫助你管理跨元件的資料。例如:
-
情境 1:你在一個購物車應用程式中,需要在多個頁面顯示購物車的商品數量。如果只用 React 的 useState,你需要透過 props 將狀態傳遞給多個元件,這會變得很麻煩。
-
情境 2:當應用程式變大,狀態邏輯變複雜時,單純的 useState 或 useContext 可能難以維護。
RTK 提供了一個集中式的「狀態倉庫」(store),讓所有元件都能存取和更新狀態,保持資料一致性。
Redux Toolkit 的核心概念
在開始實作之前,先了解幾個 RTK 的核心概念:
-
Store(狀態倉庫):
-
Store 是存放應用程式全局狀態的地方,類似一個大物件,包含所有需要管理的資料。
-
每個應用程式只有一個 store。
-
-
Slice(狀態切片):
-
Slice 是狀態的一部分,包含相關的狀態資料和操作這些資料的邏輯(reducer 和 action)。
-
例如,購物車的狀態可以是一個 slice,包含商品清單和總金額。
-
-
Action(動作):
-
Action 是一個物件,描述「要做什麼」,例如「新增商品到購物車」。
-
RTK 會自動生成 action creators,讓你不用手動撰寫。
-
-
Reducer(減速器):
-
Reducer 是一組函數,定義如何根據 action 更新狀態。
-
RTK 的 createSlice 讓你用簡單的方式定義 reducer。
-
-
Dispatch(派發):
- 當你想更新狀態時,會「派發」一個 action 到 store,store 會根據 reducer 更新狀態。
-
Selector(選擇器):
- Selector 是一個函數,用來從 store 中提取特定狀態資料,方便元件使用。
開始使用 Redux Toolkit
以下是一個簡單的購物車範例,逐步展示如何在 React 專案中使用 RTK。我們會實作一個功能:讓使用者可以新增商品到購物車,並顯示購物車的商品數量。
步驟 1:建立 React 專案並安裝 Redux Toolkit
-
建立 React 專案(如果你還沒有專案): 使用 Create React App 建立一個新的 React 專案:
npx create-react-app my-shopping-cart
cd my-shopping-cart -
安裝 Redux Toolkit 和 React-Redux: 在終端機中執行以下命令,安裝 RTK 和 React-Redux(用於連繫 React 和 Redux):
npm install @reduxjs/toolkit react-redux
步驟 2:建立 Redux Store
-
創建 Store 檔案: 在 src 資料夾中建立一個 store.js 檔案,用來設定 Redux 的 store。
// src/store.js
import { configureStore } from "@reduxjs/toolkit";
// 建立 Redux store
const store = configureStore({
reducer: {}, // 稍後我們會在這裡加入 reducer
});
export default store;-
configureStore 是 RTK 提供的函數,用來建立 store。
-
目前 reducer 是空的,我們會在下一步加入。
-
-
將 Store 連接到 React 應用程式: 修改 src/index.js,用 Provider 元件將 store 提供給整個應用程式。
javascript
// src/index.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { Provider } from "react-redux";
import store from "./store";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<App />
</Provider>
);<Provider store={store}>讓所有 React 元件都能存取 store。
步驟 3:建立購物車 Slice
我們會建立一個購物車的 slice,管理商品清單和相關操作。
-
創建 Slice 檔案: 在 src 資料夾中建立一個 features/cart/cartSlice.js 檔案。
// src/features/cart/cartSlice.js
import { createSlice } from "@reduxjs/toolkit";
// 定義初始狀態
const initialState = {
items: [], // 儲存購物車中的商品
totalQuantity: 0, // 購物車商品總數量
};
// 建立 cart slice
const cartSlice = createSlice({
name: "cart", // slice 的名稱
initialState, // 初始狀態
reducers: {
// 定義 action 和 reducer
addItem(state, action) {
const newItem = action.payload; // 從 action 取得新商品資料
const existingItem = state.items.find(
(item) => item.id === newItem.id
);
if (existingItem) {
// 如果商品已存在,增加數量
existingItem.quantity += 1;
} else {
// 如果商品不存在,新增到 items 陣列
state.items.push({ ...newItem, quantity: 1 });
}
// 更新總數量
state.totalQuantity += 1;
},
removeItem(state, action) {
const id = action.payload; // 從 action 取得商品 ID
const existingItem = state.items.find((item) => item.id === id);
if (existingItem) {
state.totalQuantity -= existingItem.quantity;
state.items = state.items.filter((item) => item.id !== id);
}
},
},
});
// 導出 actions
export const { addItem, removeItem } = cartSlice.actions;
// 導出 reducer
export default cartSlice.reducer;-
createSlice 自動生成 action creators 和 reducer。
-
initialState 定義購物車的初始狀態,包含 items(商品陣列)和 totalQuantity(總數量)。
-
addItem 和 removeItem 是兩個 reducer 函數,處理新增和移除商品的邏輯。
-
RTK 使用 Immer 庫,讓你可以用「直接修改」狀態的方式撰寫 reducer(例如 state.items.push),而不用手動複製狀態。
-
-
將 Slice 連接到 Store: 修改 src/store.js,將 cart slice 的 reducer 加入 store。
// src/store.js
import { configureStore } from "@reduxjs/toolkit";
import cartReducer from "./features/cart/cartSlice";
const store = configureStore({
reducer: {
cart: cartReducer, // 將 cart slice 的 reducer 加入
},
});
export default store;
步驟 4:建立購物車元件
我們會建立兩個元件:
-
ProductList:顯示商品清單,讓使用者新增商品到購物車。
-
Cart:顯示購物車中的商品和總數量。
-
建立 ProductList 元件: 在 src/components 資料夾中建立 ProductList.js。
// src/components/ProductList.js
import React from "react";
import { useDispatch } from "react-redux";
import { addItem } from "../features/cart/cartSlice";
const products = [
{ id: 1, name: "手機", price: 10000 },
{ id: 2, name: "筆電", price: 30000 },
{ id: 3, name: "耳機", price: 2000 },
];
function ProductList() {
const dispatch = useDispatch(); // 取得 dispatch 函數
const handleAddToCart = (product) => {
dispatch(addItem(product)); // 派發 addItem action
};
return (
<div>
<h2>商品清單</h2>
<ul>
{products.map((product) => (
<li key={product.id}>
{product.name} - NT${product.price}
<button onClick={() => handleAddToCart(product)}>
加入購物車
</button>
</li>
))}
</ul>
</div>
);
}
export default ProductList;-
useDispatch 是 React-Redux 的 Hook,用來取得 dispatch 函數。
-
點擊「加入購物車」按鈕時,呼叫 dispatch(addItem(product)),將商品資料傳給 reducer。
-
-
建立 Cart 元件: 在 src/components 資料夾中建立 Cart.js。
// src/components/Cart.js
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { removeItem } from "../features/cart/cartSlice";
function Cart() {
const dispatch = useDispatch();
const { items, totalQuantity } = useSelector((state) => state.cart); // 從 store 取得狀態
return (
<div>
<h2>購物車 (總數量: {totalQuantity})</h2>
{items.length === 0 ? (
<p>購物車是空的</p>
) : (
<ul>
{items.map((item) => (
<li key={item.id}>
{item.name} - NT${item.price} x {item.quantity}
<button onClick={() => dispatch(removeItem(item.id))}>
移除
</button>
</li>
))}
</ul>
)}
</div>
);
}
export default Cart;-
useSelector 是 React-Redux 的 Hook,用來從 store 提取狀態(這裡提取 cart 的 items 和 totalQuantity)。
-
點擊「移除」按鈕時,派發 removeItem action,傳入商品的 ID。
-
-
將元件組合到 App.js: 修改 src/App.js,引入 ProductList 和 Cart 元件。
// src/App.js
import React from "react";
import ProductList from "./components/ProductList";
import Cart from "./components/Cart";
function App() {
return (
<div>
<h1>簡單購物車</h1>
<ProductList />
<Cart />
</div>
);
}
export default App;
步驟 5:執行應用程式
在終端機中執行以下命令,啟動 React 專案:
npm start
打開瀏覽器,訪問 http://localhost:3000 ,你應該可以看到:
-
商品清單,點擊「加入購物車」可以將商品加入購物車。
-
購物車顯示當前商品和總數量,點擊「移除」可以移除商品。
進階功能:使用 RTK Query 處理 API 請求
RTK 還提供了一個強大的工具叫 RTK Query,用於簡化與後端 API 的資料請求(例如取得商品資料)。以下是簡單的範例,展示如何使用 RTK Query。
步驟 1:建立 API Slice
在 src/features/api/apiSlice.js 中建立一個 API slice:
// src/features/api/apiSlice.js
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
export const apiSlice = createApi({
reducerPath: "api",
baseQuery: fetchBaseQuery({ baseUrl: "https://fakestoreapi.com" }), // 使用假 API
endpoints: (builder) => ({
getProducts: builder.query({
query: () => "/products",
}),
}),
});
export const { useGetProductsQuery } = apiSlice;
-
createApi 是 RTK Query 的核心函數,用來定義 API 請求。
-
fetchBaseQuery 是 RTK Query 提供的基礎查詢工具,這裡使用一個假的 API(https://fakestoreapi.com)。
-
getProducts 是一個查詢端點,用來取得商品資料。
步驟 2:將 API Slice 加入 Store
修改 src/store.js,加入 API slice 的 reducer:
javascript
// src/store.js
import { configureStore } from '@reduxjs/toolkit';
import cartReducer from './features/cart/cartSlice';
import { apiSlice } from './features/api/apiSlice';
const store = configureStore({
reducer: {
cart: cartReducer,
[apiSlice.reducerPath]: apiSlice.reducer, // 加入 API slice 的 reducer
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(apiSlice.middleware), // 加入 RTK Query 的 middleware
});
export default store;
步驟 3:更新 ProductList 元件以使用 API
修改 src/components/ProductList.js,使用 RTK Query 取得商品資料:
javascript
// src/components/ProductList.js
import React from 'react';
import { useDispatch } from 'react-redux';
import { addItem } from '../features/cart/cartSlice';
import { useGetProductsQuery } from '../features/api/apiSlice';
function ProductList() {
const dispatch = useDispatch();
const { data: products, isLoading, error } = useGetProductsQuery(); // 使用 RTK Query 取得資料
if (isLoading) return <p>載入中...</p>;
if (error) return <p>錯誤:{error.message}</p>;
const handleAddToCart = (product) => {
dispatch(addItem({ id: product.id, name: product.title, price: product.price }));
};
return (
<div>
<h2>商品清單</h2>
<ul>
{products?.map((product) => (
<li key={product.id}>
{product.title} - ${product.price}
<button onClick={() => handleAddToCart(product)}>加入購物車</button>
</li>
))}
</ul>
</div>
);
}
export default ProductList;
-
useGetProductsQuery 是 RTK Query 自動生成的 Hook,用來執行 getProducts 查詢。
-
data、isLoading 和 error 是 Hook 提供的狀態,方便處理載入和錯誤情況。
總結
透過以上步驟,你已經學會如何使用 Redux Toolkit 在 React 應用程式中管理狀態,並結合 RTK Query 處理 API 請求。以下是重點回顧:
-
Redux Toolkit 的優勢:
-
簡化 Redux 的設定和程式碼。
-
提供 createSlice 自動生成 action 和 reducer。
-
內建 Immer,讓狀態更新更直觀。
-
-
核心流程:
-
建立 store 和 slice,定義狀態和操作邏輯。
-
使用 Provider 將 store 連接到 React 應用程式。
-
使用 useSelector 和 useDispatch 在元件中存取和更新狀態。
-
-
RTK Query:
-
簡化 API 請求,自動處理快取、載入狀態和錯誤處理。
-
使用 createApi 定義端點,並透過自動生成的 Hook 存取資料。
-