Skip to main content

什麼是 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 的核心概念:

  1. Store(狀態倉庫)

    • Store 是存放應用程式全局狀態的地方,類似一個大物件,包含所有需要管理的資料。

    • 每個應用程式只有一個 store。

  2. Slice(狀態切片)

    • Slice 是狀態的一部分,包含相關的狀態資料和操作這些資料的邏輯(reducer 和 action)。

    • 例如,購物車的狀態可以是一個 slice,包含商品清單和總金額。

  3. Action(動作)

    • Action 是一個物件,描述「要做什麼」,例如「新增商品到購物車」。

    • RTK 會自動生成 action creators,讓你不用手動撰寫。

  4. Reducer(減速器)

    • Reducer 是一組函數,定義如何根據 action 更新狀態。

    • RTK 的 createSlice 讓你用簡單的方式定義 reducer。

  5. Dispatch(派發)

    • 當你想更新狀態時,會「派發」一個 action 到 store,store 會根據 reducer 更新狀態。
  6. Selector(選擇器)

    • Selector 是一個函數,用來從 store 中提取特定狀態資料,方便元件使用。

開始使用 Redux Toolkit

以下是一個簡單的購物車範例,逐步展示如何在 React 專案中使用 RTK。我們會實作一個功能:讓使用者可以新增商品到購物車,並顯示購物車的商品數量。

步驟 1:建立 React 專案並安裝 Redux Toolkit

  1. 建立 React 專案(如果你還沒有專案): 使用 Create React App 建立一個新的 React 專案:

    npx create-react-app my-shopping-cart
    cd my-shopping-cart
  2. 安裝 Redux Toolkit 和 React-Redux: 在終端機中執行以下命令,安裝 RTK 和 React-Redux(用於連繫 React 和 Redux):

    npm install @reduxjs/toolkit react-redux

步驟 2:建立 Redux Store

  1. 創建 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 是空的,我們會在下一步加入。

  2. 將 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,管理商品清單和相關操作。

  1. 創建 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),而不用手動複製狀態。

  2. 將 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:顯示購物車中的商品和總數量。

  1. 建立 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。

  2. 建立 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。

  3. 將元件組合到 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 請求。以下是重點回顧:

  1. Redux Toolkit 的優勢

    • 簡化 Redux 的設定和程式碼。

    • 提供 createSlice 自動生成 action 和 reducer。

    • 內建 Immer,讓狀態更新更直觀。

  2. 核心流程

    • 建立 store 和 slice,定義狀態和操作邏輯。

    • 使用 Provider 將 store 連接到 React 應用程式。

    • 使用 useSelector 和 useDispatch 在元件中存取和更新狀態。

  3. RTK Query

    • 簡化 API 請求,自動處理快取、載入狀態和錯誤處理。

    • 使用 createApi 定義端點,並透過自動生成的 Hook 存取資料。