引言:為什麼需要 Redux?

在 iOS 開發中,隨著應用規模擴大,狀態管理逐漸成為最具挑戰性的課題。當多個 View 需要共享狀態、狀態變化難以追蹤時,應用很容易陷入混亂。

Redux 作為一種可預測的狀態容器,最早在 JavaScript 生態系中流行,如今也廣泛應用於 Swift/iOS 專案。本文將深入介紹 Redux 架構的核心觀念,包含:

  • Reducer(減少器):狀態更新的核心邏輯
  • Store(儲存區):應用的單一狀態來源
  • Action(動作):描述「發生什麼事」的指令
  • Middleware(中介層):處理非同步與副作用

Redux 核心架構概覽

架構組成

flowchart TB
    View[View / SwiftUI]
    Store[Store<br/>單一狀態來源]
    Reducer[Reducer<br/>純函數]
    State[State<br/>應用狀態]
    Action[Action<br/>動作描述]
    Middleware[Middleware<br/>非同步處理]

    View -->|1. dispatch| Action
    Action -->|2. 觸發| Middleware
    Middleware -->|3. 可能派發新 Action| Action
    Action -->|4. 傳遞| Reducer
    Reducer -->|5. 計算| State
    State -->|6. 更新| Store
    Store -->|7. 觀察 @Published| View

    style Store fill:#4ade80
    style Reducer fill:#60a5fa
    style Middleware fill:#f97316

架構特性:

  • 單向資料流:資料流向可預測
  • 單一狀態來源:整個應用只有一個 State 樹
  • 狀態不可變:不直接修改 State,而是創建新 State
  • 可測試性高:Reducer 是純函數,易於測試

核心概念 1:State(狀態)

State 是什麼?

State 是整個應用的單一資料來源(Single Source of Truth)。它通常是一個 struct,描述當前應用的完整狀態。

實作範例

// AppState.swift
struct AppState {
    // 購物車
    var cartItems: [CartItem] = []
    var totalAmount: Decimal = 0.0

    // 用戶資訊
    var userProfile: UserProfile?
    var isLoggedIn: Bool = false

    // UI 狀態
    var isLoading: Bool = false
    var errorMessage: String?

    // 套餐選擇
    var packages: [Package] = []
    var selectedPackageId: String?
}

// 購物車商品
struct CartItem: Identifiable {
    let id: String
    let name: String
    let price: Decimal
    var quantity: Int
}

// 用戶資料
struct UserProfile {
    let id: String
    let name: String
    let email: String
}

// 套餐
struct Package: Identifiable {
    let id: String
    let name: String
    let items: [PackageItem]
}

struct PackageItem: Identifiable {
    let id: String
    let name: String
    var quantity: Int
}

設計原則:

  • ✅ 使用 struct(值類型)確保不可變性
  • ✅ 扁平化設計,避免過深的巢狀結構
  • ✅ 符合 Codable 協議,便於序列化

核心概念 2:Action(動作)

Action 是什麼?

Action 是一個明確描述「發生了什麼事情」的指令。它通常是一個 enum,並攜帶必要的參數。

實作範例

// AppAction.swift
enum AppAction {
    // 購物車相關
    case updateItemQuantity(itemId: String, quantity: Int)
    case confirmAddToCart
    case clearCart

    // 套餐相關
    case updatePackageItemQuantity(packageId: String, itemId: String, quantity: Int)
    case selectPackage(packageId: String)

    // 用戶相關
    case loginRequest(username: String, password: String)
    case loginSuccess(user: UserProfile)
    case loginFailed(error: String)
    case logout

    // UI 狀態
    case setLoading(Bool)
    case setError(String?)
}

Action 設計原則:

  • 描述性命名:清楚表達「發生什麼事」(例如 loginSuccess 而不是 login
  • 攜帶必要參數:使用關聯值(Associated Values)
  • 不包含邏輯:Action 只是「事件描述」,不執行任何邏輯

核心概念 3:Reducer(減少器)

Reducer 是什麼?

Reducer 是 Redux 架構的核心,它是一個純函數(Pure Function),負責根據不同的 Action,計算並回傳新的 State。

函數簽名:

(State, Action) -> State

規則:

  1. 純函數:相同輸入永遠產生相同輸出
  2. 不可變性:不直接修改原 State,而是回傳新 State
  3. 無副作用:不進行 API 呼叫、不修改外部變數

完整實作

// AppReducer.swift
func appReducer(state: AppState, action: AppAction) -> AppState {
    var newState = state

    switch action {
    // ============================================
    // 購物車相關
    // ============================================
    case .updateItemQuantity(let itemId, let quantity):
        if let index = newState.cartItems.firstIndex(where: { $0.id == itemId }) {
            if quantity > 0 {
                newState.cartItems[index].quantity = quantity
            } else {
                // 數量為 0,移除商品
                newState.cartItems.remove(at: index)
            }
        }
        // 重新計算總金額
        newState.totalAmount = newState.cartItems.reduce(0) { total, item in
            total + (item.price * Decimal(item.quantity))
        }

    case .confirmAddToCart:
        // 將選中的套餐商品加入購物車
        if let packageId = newState.selectedPackageId,
           let package = newState.packages.first(where: { $0.id == packageId }) {

            for item in package.items where item.quantity > 0 {
                let cartItem = CartItem(
                    id: item.id,
                    name: item.name,
                    price: 10.0, // 假設價格
                    quantity: item.quantity
                )
                newState.cartItems.append(cartItem)
            }

            // 重新計算總金額
            newState.totalAmount = newState.cartItems.reduce(0) { total, item in
                total + (item.price * Decimal(item.quantity))
            }

            // 清空套餐選擇
            newState.selectedPackageId = nil
        }

    case .clearCart:
        newState.cartItems = []
        newState.totalAmount = 0.0

    // ============================================
    // 套餐相關
    // ============================================
    case .updatePackageItemQuantity(let packageId, let itemId, let quantity):
        if let packageIndex = newState.packages.firstIndex(where: { $0.id == packageId }),
           let itemIndex = newState.packages[packageIndex].items.firstIndex(where: { $0.id == itemId }) {
            newState.packages[packageIndex].items[itemIndex].quantity = max(0, quantity)
        }

    case .selectPackage(let packageId):
        newState.selectedPackageId = packageId

    // ============================================
    // 用戶相關
    // ============================================
    case .loginSuccess(let user):
        newState.userProfile = user
        newState.isLoggedIn = true
        newState.isLoading = false
        newState.errorMessage = nil

    case .loginFailed(let error):
        newState.userProfile = nil
        newState.isLoggedIn = false
        newState.isLoading = false
        newState.errorMessage = error

    case .logout:
        newState.userProfile = nil
        newState.isLoggedIn = false
        newState.cartItems = []
        newState.totalAmount = 0.0

    // ============================================
    // UI 狀態
    // ============================================
    case .setLoading(let loading):
        newState.isLoading = loading

    case .setError(let error):
        newState.errorMessage = error

    default:
        break
    }

    return newState
}

為什麼叫 Reducer?

名稱來自 JavaScript 的 Array.reduce() 方法:

// JavaScript 範例
[1, 2, 3, 4].reduce((acc, value) => acc + value, 0)
// 結果: 10

在 Redux 中,我們可以想像狀態的更新過程,就像是一系列的 Action 透過 Reducer「歸納」成最新的應用狀態:

// 概念示意
actions.reduce((當前狀態, 動作) => 新狀態, 初始狀態)

因此 Reducer 就是應用邏輯的「歸納器」。


核心概念 4:Store(儲存區)

Store 是什麼?

Store 是 Redux 的核心容器,負責:

  1. ✅ 保存應用的狀態樹(State)
  2. ✅ 提供 dispatch(action:) 方法派送 Action
  3. ✅ 呼叫 Reducer 更新狀態
  4. ✅ 通知 View 狀態變化(透過 @Published

基本實作

// Store.swift
final class Store: ObservableObject {
    // 使用 @Published 讓 SwiftUI 自動觀察
    @Published private(set) var state: AppState

    // Reducer 函數
    private let reducer: (AppState, AppAction) -> AppState

    init(
        initialState: AppState = AppState(),
        reducer: @escaping (AppState, AppAction) -> AppState
    ) {
        self.state = initialState
        self.reducer = reducer
    }

    // 派送 Action
    func dispatch(_ action: AppAction) {
        print("[Store] Dispatching action: \(action)")
        state = reducer(state, action)
        print("[Store] New state: \(state)")
    }
}

使用範例

// 初始化 Store
let store = Store(
    initialState: AppState(),
    reducer: appReducer
)

// 派送 Action
store.dispatch(.loginRequest(username: "user", password: "pass"))
store.dispatch(.updateItemQuantity(itemId: "123", quantity: 2))

核心概念 5:View(視圖整合)

SwiftUI 整合

在 SwiftUI 中,View 透過 @EnvironmentObject@ObservedObject 監聽 Store 的狀態變化。

完整範例

// App 入口
@main
struct MyApp: App {
    // 建立全域 Store
    @StateObject private var store = Store(
        initialState: AppState(),
        reducer: appReducer,
        middlewares: [loggingMiddleware, apiMiddleware]
    )

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(store)
        }
    }
}

// ============================================
// 購物車 View
// ============================================
struct CartView: View {
    @EnvironmentObject var store: Store

    var body: some View {
        List {
            ForEach(store.state.cartItems) { item in
                HStack {
                    Text(item.name)
                    Spacer()
                    Text("\(item.quantity)")

                    Stepper("", value: Binding(
                        get: { item.quantity },
                        set: { newValue in
                            store.dispatch(.updateItemQuantity(
                                itemId: item.id,
                                quantity: newValue
                            ))
                        }
                    ))
                }
            }

            // 總金額
            HStack {
                Text("總計")
                    .font(.headline)
                Spacer()
                Text("$\(store.state.totalAmount)")
                    .font(.headline)
            }
        }
        .navigationTitle("購物車")
    }
}

// ============================================
// 登入 View
// ============================================
struct LoginView: View {
    @EnvironmentObject var store: Store
    @State private var username = ""
    @State private var password = ""

    var body: some View {
        VStack {
            TextField("使用者名稱", text: $username)
                .textFieldStyle(.roundedBorder)
                .padding()

            SecureField("密碼", text: $password)
                .textFieldStyle(.roundedBorder)
                .padding()

            if store.state.isLoading {
                ProgressView()
            } else {
                Button("登入") {
                    store.dispatch(.loginRequest(
                        username: username,
                        password: password
                    ))
                }
                .buttonStyle(.borderedProminent)
            }

            if let error = store.state.errorMessage {
                Text(error)
                    .foregroundColor(.red)
                    .padding()
            }
        }
    }
}

View 設計原則:

  • View 不直接修改 State:所有變更透過 dispatch(action)
  • 單向資料流:View → Action → Reducer → State → View
  • 自動更新@Published state 變化自動觸發 View 重繪

Redux 資料流詳解

完整流程圖

sequenceDiagram
    participant View as SwiftUI View
    participant Store as Store
    participant Middleware as Middleware
    participant Reducer as Reducer
    participant State as State

    View->>Store: 1. dispatch(.loginRequest)

    Store->>Middleware: 2. 攔截 Action
    Note over Middleware: 處理非同步邏輯<br/>(API 呼叫)

    Middleware->>Middleware: 3. 執行 async task
    Note over Middleware: await AuthService.login()

    Middleware->>Store: 4. dispatch(.loginSuccess)

    Store->>Reducer: 5. reducer(state, action)
    Note over Reducer: 純函數計算新狀態<br/>newState.userProfile = user

    Reducer-->>Store: 6. 回傳新 State
    Store->>State: 7. state = newState
    Note over Store: @Published 觸發通知

    State-->>View: 8. SwiftUI 自動重繪
    Note over View: body 重新計算

流程說明:

  1. 用戶操作:點擊「登入」按鈕
  2. View 派送 Actionstore.dispatch(.loginRequest(...))
  3. Middleware 攔截:檢查是否需要處理非同步邏輯
  4. 執行非同步任務:呼叫 API AuthService.login()
  5. 派送成功 Actiondispatch(.loginSuccess(user))
  6. Reducer 計算新狀態newState.userProfile = user
  7. Store 更新狀態state = newState(觸發 @Published
  8. View 自動重繪:SwiftUI 偵測到 state 變化,重新計算 body

Middleware(中介層)深入解析

為什麼需要 Middleware?

Reducer 必須是純函數,不能包含副作用(Side Effects):

  • ❌ API 呼叫
  • ❌ 資料庫存取
  • ❌ Timer / 延遲執行
  • ❌ 隨機數生成

Middleware 提供了一個安全的地方來處理這些非純函數邏輯。

Middleware 類型定義

// Middleware.swift
typealias Middleware = (
    AppState,                        // 當前狀態
    AppAction,                       // 當前 Action
    @escaping (AppAction) -> Void    // dispatch 函數
) -> Void

實作範例:日誌 Middleware

// LoggingMiddleware.swift
let loggingMiddleware: Middleware = { state, action, dispatch in
    print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
    print("📤 [Action] \(action)")
    print("📊 [State Before]")
    print("   - isLoggedIn: \(state.isLoggedIn)")
    print("   - cartItems: \(state.cartItems.count) items")
    print("   - totalAmount: $\(state.totalAmount)")
    print("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
}

實作範例:API Middleware

// APIMiddleware.swift
let apiMiddleware: Middleware = { state, action, dispatch in
    switch action {
    case .loginRequest(let username, let password):
        // 設定 Loading 狀態
        dispatch(.setLoading(true))

        Task {
            do {
                // 模擬非同步 API 請求
                let user = try await AuthService.login(
                    username: username,
                    password: password
                )

                // 登入成功
                await MainActor.run {
                    dispatch(.loginSuccess(user: user))
                }

            } catch {
                // 登入失敗
                await MainActor.run {
                    dispatch(.loginFailed(error: error.localizedDescription))
                }
            }
        }

    default:
        break
    }
}

// 模擬 Auth Service
struct AuthService {
    static func login(username: String, password: String) async throws -> UserProfile {
        // 模擬網路延遲
        try await Task.sleep(nanoseconds: 1_000_000_000) // 1 秒

        if username == "admin" && password == "password" {
            return UserProfile(
                id: "user123",
                name: "Admin User",
                email: "admin@example.com"
            )
        } else {
            throw NSError(
                domain: "AuthService",
                code: 401,
                userInfo: [NSLocalizedDescriptionKey: "帳號或密碼錯誤"]
            )
        }
    }
}

整合 Middleware 到 Store

// Store.swift(支援 Middleware)
final class Store: ObservableObject {
    @Published private(set) var state: AppState
    private let reducer: (AppState, AppAction) -> AppState
    private let middlewares: [Middleware]

    init(
        initialState: AppState = AppState(),
        reducer: @escaping (AppState, AppAction) -> AppState,
        middlewares: [Middleware] = []
    ) {
        self.state = initialState
        self.reducer = reducer
        self.middlewares = middlewares
    }

    func dispatch(_ action: AppAction) {
        // 1. 先讓 Middleware 處理
        for middleware in middlewares {
            middleware(state, action, dispatch)
        }

        // 2. 再由 Reducer 更新狀態
        state = reducer(state, action)
    }
}

使用範例:

let store = Store(
    initialState: AppState(),
    reducer: appReducer,
    middlewares: [
        loggingMiddleware,  // 日誌記錄
        apiMiddleware       // API 呼叫
    ]
)

複雜場景:串接多個 API 的策略

當需要串接多個 API(例如先登入 → 取得用戶資料 → 載入訂單列表),有三種主流做法:

策略比較

flowchart TD
    Problem[需求:登入 → 取得資料 → 載入訂單]

    Problem --> Strategy1[策略 1:巢狀呼叫]
    Problem --> Strategy2[策略 2:合併 Action]
    Problem --> Strategy3[策略 3:多層 Middleware]

    Strategy1 --> S1_Pro[✅ 邏輯集中]
    Strategy1 --> S1_Con[❌ 難以閱讀]

    Strategy2 --> S2_Pro[✅ 流程扁平]
    Strategy2 --> S2_Con[❌ Action 語意過大]

    Strategy3 --> S3_Pro[✅ 清楚模組化]
    Strategy3 --> S3_Con[⚠️ 需良好設計]

    style Strategy3 fill:#4ade80

策略 1:巢狀呼叫

流程圖:

sequenceDiagram
    participant View
    participant Middleware
    participant API as Auth API

    View->>Middleware: loginRequest

    Middleware->>API: login()
    API-->>Middleware: user

    Middleware->>Middleware: ❌ 巢狀<br/>getUserData()
    Middleware->>Middleware: ❌ 巢狀<br/>getOrders()

    Middleware->>View: sessionInitialized

程式碼:

let nestedMiddleware: Middleware = { state, action, dispatch in
    switch action {
    case .loginRequest(let username, let password):
        Task {
            // 1. 登入
            let user = try await AuthService.login(username: username, password: password)
            dispatch(.loginSuccess(user: user))

            // 2. 取得用戶資料(巢狀)
            let userData = try await UserService.getUserData(userId: user.id)
            dispatch(.userDataLoaded(userData))

            // 3. 載入訂單(巢狀)
            let orders = try await OrderService.getOrders(userId: user.id)
            dispatch(.ordersLoaded(orders))
        }
    default:
        break
    }
}

優點:

  • ✅ 邏輯集中在一處

缺點:

  • ❌ 程式碼層層巢狀,難以維護
  • ❌ 錯誤處理複雜
  • ❌ 無法單獨測試各階段

策略 2:合併 Action

流程圖:

flowchart LR
    A[initializeUserSession] --> B[Middleware]
    B --> C[login]
    C --> D[getUserData]
    D --> E[getOrders]
    E --> F[sessionInitialized]

    style A fill:#60a5fa
    style F fill:#4ade80

程式碼:

enum AppAction {
    case initializeUserSession(username: String, password: String)
    case sessionInitialized(user: UserProfile, orders: [Order])
    // ...
}

let combinedMiddleware: Middleware = { state, action, dispatch in
    switch action {
    case .initializeUserSession(let username, let password):
        Task {
            let user = try await AuthService.login(username: username, password: password)
            let userData = try await UserService.getUserData(userId: user.id)
            let orders = try await OrderService.getOrders(userId: user.id)

            // 一次性派發包含所有資料的 Action
            dispatch(.sessionInitialized(user: user, orders: orders))
        }
    default:
        break
    }
}

優點:

  • ✅ 流程扁平,一次處理

缺點:

  • ❌ Action 語意過於龐大(違反單一職責)
  • ❌ 無法追蹤中間狀態
  • ❌ 難以在 UI 顯示進度(例如「載入訂單中…」)

策略 3:多層 Middleware(推薦)

流程圖:

sequenceDiagram
    participant View
    participant M1 as Middleware 1<br/>(Login)
    participant M2 as Middleware 2<br/>(UserData)
    participant M3 as Middleware 3<br/>(Orders)
    participant Store

    View->>M1: loginRequest

    M1->>M1: login()
    M1->>Store: loginSuccess

    Store->>M2: 偵測 loginSuccess
    M2->>M2: getUserData()
    M2->>Store: userDataLoaded

    Store->>M3: 偵測 userDataLoaded
    M3->>M3: getOrders()
    M3->>Store: ordersLoaded

    Store-->>View: 完成

程式碼:

// ============================================
// Middleware 1: 處理登入
// ============================================
let loginMiddleware: Middleware = { state, action, dispatch in
    switch action {
    case .loginRequest(let username, let password):
        dispatch(.setLoading(true))

        Task {
            do {
                let user = try await AuthService.login(username: username, password: password)
                await MainActor.run {
                    dispatch(.loginSuccess(user: user))
                }
            } catch {
                await MainActor.run {
                    dispatch(.loginFailed(error: error.localizedDescription))
                }
            }
        }
    default:
        break
    }
}

// ============================================
// Middleware 2: 偵測登入成功,載入用戶資料
// ============================================
let userDataMiddleware: Middleware = { state, action, dispatch in
    switch action {
    case .loginSuccess(let user):
        Task {
            let userData = try await UserService.getUserData(userId: user.id)
            await MainActor.run {
                dispatch(.userDataLoaded(userData))
            }
        }
    default:
        break
    }
}

// ============================================
// Middleware 3: 偵測資料載入完成,載入訂單
// ============================================
let ordersMiddleware: Middleware = { state, action, dispatch in
    switch action {
    case .userDataLoaded:
        guard let userId = state.userProfile?.id else { return }

        Task {
            let orders = try await OrderService.getOrders(userId: userId)
            await MainActor.run {
                dispatch(.ordersLoaded(orders))
                dispatch(.setLoading(false))
            }
        }
    default:
        break
    }
}

使用:

let store = Store(
    initialState: AppState(),
    reducer: appReducer,
    middlewares: [
        loggingMiddleware,
        loginMiddleware,      // 處理登入
        userDataMiddleware,   // 偵測登入成功 → 載入用戶資料
        ordersMiddleware      // 偵測資料載入 → 載入訂單
    ]
)

優點:

  • 模組化清楚:每個 Middleware 只負責一件事
  • 易於測試:可單獨測試各 Middleware
  • 可追蹤進度:每個階段都有對應的 Action
  • 易於擴展:新增功能只需新增 Middleware

缺點:

  • ⚠️ 需要良好的設計,避免 Middleware 之間過度耦合

最佳實踐與建議

1. Reducer 設計

// ✅ 好的做法
func appReducer(state: AppState, action: AppAction) -> AppState {
    var newState = state

    switch action {
    case .loginSuccess(let user):
        newState.userProfile = user
        newState.isLoggedIn = true
        return newState  // 明確回傳

    default:
        return state     // 未知 Action 回傳原狀態
    }
}

// ❌ 避免的做法
func badReducer(state: AppState, action: AppAction) -> AppState {
    switch action {
    case .loginSuccess(let user):
        state.userProfile = user  // ❌ 直接修改 state(如果是 class)
        return state

    default:
        return AppState()  // ❌ 回傳新的初始狀態,會丟失其他資料
    }
}

2. State 結構設計

// ✅ 扁平化設計
struct AppState {
    var user: UserProfile?
    var cartItemIds: [String]           // 只存 ID
    var cartItemsById: [String: CartItem]  // ID 對應實體
}

// ❌ 過深的巢狀
struct BadAppState {
    var data: DataContainer
}

struct DataContainer {
    var user: UserContainer
}

struct UserContainer {
    var profile: UserProfile
}

3. Middleware 錯誤處理

let safeAPIMiddleware: Middleware = { state, action, dispatch in
    switch action {
    case .loginRequest(let username, let password):
        Task {
            do {
                let user = try await AuthService.login(username: username, password: password)
                await MainActor.run {
                    dispatch(.loginSuccess(user: user))
                }
            } catch let error as NSError {
                // ✅ 詳細錯誤處理
                await MainActor.run {
                    let errorMessage: String
                    switch error.code {
                    case 401:
                        errorMessage = "帳號或密碼錯誤"
                    case 500:
                        errorMessage = "伺服器錯誤,請稍後再試"
                    default:
                        errorMessage = error.localizedDescription
                    }
                    dispatch(.loginFailed(error: errorMessage))
                }
            }
        }
    default:
        break
    }
}

4. 測試範例

import XCTest

class AppReducerTests: XCTestCase {
    func testLoginSuccess() {
        // Given
        let initialState = AppState()
        let user = UserProfile(id: "123", name: "Test User", email: "test@example.com")
        let action = AppAction.loginSuccess(user: user)

        // When
        let newState = appReducer(state: initialState, action: action)

        // Then
        XCTAssertTrue(newState.isLoggedIn)
        XCTAssertEqual(newState.userProfile?.name, "Test User")
        XCTAssertNil(newState.errorMessage)
    }

    func testCartQuantityUpdate() {
        // Given
        var initialState = AppState()
        initialState.cartItems = [
            CartItem(id: "item1", name: "商品A", price: 10.0, quantity: 1)
        ]

        // When
        let newState = appReducer(
            state: initialState,
            action: .updateItemQuantity(itemId: "item1", quantity: 3)
        )

        // Then
        XCTAssertEqual(newState.cartItems.first?.quantity, 3)
        XCTAssertEqual(newState.totalAmount, 30.0)
    }
}

進階主題:組合 Reducer

當應用規模擴大時,可以將大型 Reducer 拆分為多個小 Reducer:

// 購物車 Reducer
func cartReducer(state: AppState, action: AppAction) -> AppState {
    var newState = state

    switch action {
    case .updateItemQuantity, .confirmAddToCart, .clearCart:
        // 處理購物車相關邏輯
        // ...
        return newState
    default:
        return state
    }
}

// 用戶 Reducer
func userReducer(state: AppState, action: AppAction) -> AppState {
    var newState = state

    switch action {
    case .loginSuccess, .loginFailed, .logout:
        // 處理用戶相關邏輯
        // ...
        return newState
    default:
        return state
    }
}

// 組合 Reducer
func appReducer(state: AppState, action: AppAction) -> AppState {
    var newState = state

    // 依序執行各子 Reducer
    newState = cartReducer(state: newState, action: action)
    newState = userReducer(state: newState, action: action)

    return newState
}

結論:Redux 的價值

關鍵優勢

可預測性

  • 狀態變化有明確的流程:Action → Reducer → State
  • 相同的 Action 序列永遠產生相同的 State

可測試性

  • Reducer 是純函數,易於單元測試
  • 不需要 Mock,直接測試輸入輸出

可維護性

  • 狀態變化邏輯集中在 Reducer
  • 單向資料流,易於追蹤問題

可除錯性

  • 透過 Logging Middleware 追蹤所有 Action
  • Time-travel debugging(重播 Action 序列)

適用場景

✅ 推薦使用 Redux:

  • 中大型應用(多個 View 共享狀態)
  • 複雜的狀態邏輯
  • 需要詳細的狀態變化追蹤
  • 團隊協作專案

⚠️ 可能過度設計:

  • 簡單的小型應用
  • 狀態變化簡單明確
  • 單頁面應用

最後建議

Redux 不是銀彈,但在適當的場景下,它能大幅提升應用的可維護性可預測性。當專案逐漸龐大時,Redux 的價值會愈發明顯。

建議學習路徑:

  1. 先掌握 Reducer 與 Store 的基本概念
  2. 理解單向資料流
  3. 實作簡單的 Middleware
  4. 嘗試多層 Middleware 的設計
  5. 學習組合 Reducer 的技巧

參考資源