MVVM vs TCA 对比

Model-View-ViewModel:直观、灵活、使用广泛

VS
TCA

The Composable Architecture:函数式、可测试、可预测

10 分钟阅读iOS

评分对比

图表加载中…

详细评分

详细评分: MVVM TCA ——按类别打分,满分 10 分
分类MVVMTCA
性能
8/10
8/10
学习难易度
9/10
4/10
生态系统
9/10
7/10
社区
9/10
7/10
就业市场
10/10
7/10
面向未来
8/10
9/10

优缺点

MVVM

优点

  • 学习门槛低——概念简单,社区资源丰富
  • 与 SwiftUI 天然契合——通过 @Observable、@ObservedObject 无缝衔接
  • 灵活——可根据项目规模和需求灵活调整
  • 可测试性好——ViewModel 可独立于 View 进行测试
  • 适用于各种规模的项目
  • 团队易于适应——iOS 之外也是广为人知的模式
  • 兼容 Combine 或 async/await

缺点

  • 大型项目容易陷入“臃肿 ViewModel”问题
  • 没有统一的标准实现——各团队理解不尽相同
  • 缺乏针对副作用(side effects)管理的明确规则
  • 协调导航、深层链接等需要额外模式支持(如 Coordinator)
  • 保持状态一致性依赖手动处理,容易出错

最适合

中小规模项目需要团队快速上手产出的项目架构经验较少的团队使用 SwiftUI + Combine 或 async/await 的项目标准企业级应用

TCA

优点

  • 单向数据流——状态完全可预测
  • 详尽测试——每个 Reducer、Effect、依赖都可测试
  • 组合式架构——大型功能由小型 Reducer 组合而成
  • 副作用(Effect)完全受控且可测试
  • Point-Free 团队积极维护,文档质量出色
  • 依赖注入在框架内标准化
  • 与 SwiftUI NavigationStack 集成(基于树的导航)

缺点

  • 学习曲线陡峭——需理解 State、Action、Reducer、Effect、Store 等概念
  • 存在样板代码——即使简单功能也需要 Action 枚举和 Reducer
  • 对小型项目而言过度设计(存在过度工程风险)
  • 编译时间——大型项目中编译耗时可能变长
  • 需要整个团队理解 TCA——混用方式会带来问题

最适合

大型复杂应用测试覆盖率至关重要的项目需要复杂导航与深层链接的场景具备函数式编程经验的团队多团队协作的长期企业级项目

代码对比

MVVM
// MVVM - 商品列表
import SwiftUI
import Observation

@Observable
class ProductListViewModel {
    var products: [Product] = []
    var isLoading = false
    var errorMessage: String?
    var searchText = ""

    private let repository: ProductRepository

    init(repository: ProductRepository = .live) {
        self.repository = repository
    }

    var filteredProducts: [Product] {
        guard !searchText.isEmpty else { return products }
        return products.filter { $0.name.localizedCaseInsensitiveContains(searchText) }
    }

    func loadProducts() async {
        isLoading = true
        errorMessage = nil
        do {
            products = try await repository.fetchProducts()
        } catch {
            errorMessage = "商品加载失败:\(error.localizedDescription)"
        }
        isLoading = false
    }

    func deleteProduct(_ product: Product) async {
        do {
            try await repository.delete(product.id)
            products.removeAll { $0.id == product.id }
        } catch {
            errorMessage = "删除失败:\(error.localizedDescription)"
        }
    }
}

struct ProductListView: View {
    @State private var viewModel = ProductListViewModel()

    var body: some View {
        NavigationStack {
            Group {
                if viewModel.isLoading {
                    ProgressView("加载中...")
                } else {
                    List(viewModel.filteredProducts) { product in
                        ProductRow(product: product)
                    }
                    .searchable(text: $viewModel.searchText)
                }
            }
            .navigationTitle("商品")
        }
        .task { await viewModel.loadProducts() }
        .alert("错误", isPresented: .constant(viewModel.errorMessage != nil)) {
            Button("确定") { viewModel.errorMessage = nil }
        } message: {
            Text(viewModel.errorMessage ?? "")
        }
    }
}
TCA
// TCA - 商品列表
import ComposableArchitecture
import SwiftUI

@Reducer
struct ProductListFeature {
    @ObservableState
    struct State: Equatable {
        var products: [Product] = []
        var isLoading = false
        var errorMessage: String?
        var searchText = ""

        var filteredProducts: [Product] {
            guard !searchText.isEmpty else { return products }
            return products.filter { $0.name.localizedCaseInsensitiveContains(searchText) }
        }
    }

    enum Action {
        case onAppear
        case searchTextChanged(String)
        case deleteProduct(id: String)
        case productsLoaded(Result<[Product], Error>)
        case productDeleted(Result<Void, Error>)
        case dismissError
    }

    @Dependency(\.productRepository) var repository

    var body: some ReducerOf<Self> {
        Reduce { state, action in
            switch action {
            case .onAppear:
                state.isLoading = true
                return .run { send in
                    await send(.productsLoaded(
                        Result { try await repository.fetchProducts() }
                    ))
                }

            case .searchTextChanged(let text):
                state.searchText = text
                return .none

            case .deleteProduct(let id):
                return .run { send in
                    await send(.productDeleted(
                        Result { try await repository.delete(id) }
                    ))
                }

            case .productsLoaded(.success(let products)):
                state.isLoading = false
                state.products = products
                return .none

            case .productsLoaded(.failure(let error)):
                state.isLoading = false
                state.errorMessage = error.localizedDescription
                return .none

            case .productDeleted(.success):
                return .send(.onAppear)

            case .productDeleted(.failure(let error)):
                state.errorMessage = error.localizedDescription
                return .none

            case .dismissError:
                state.errorMessage = nil
                return .none
            }
        }
    }
}

struct ProductListView: View {
    let store: StoreOf<ProductListFeature>

    var body: some View {
        WithPerceptionTracking {
            NavigationStack {
                List(store.filteredProducts) { product in
                    Text(product.name)
                }
                .searchable(text: store.binding(get: \.searchText, send: ProductListFeature.Action.searchTextChanged))
                .navigationTitle("商品")
            }
            .task { store.send(.onAppear) }
        }
    }
}

结论

中小型项目适合 MVVM——开发速度更快,入门门槛更低。大型、复杂且对测试要求严格的项目适合 TCA——可预测的状态管理和可组合架构的价值会在长期项目中逐渐显现。混合方案同样可行:核心功能用 MVVM,关键/复杂流程用 TCA。

获取免费咨询
常见问题

常见问题

掌握基础概念大约需要 1-2 周,达到高效产出水平大约需要 1-2 个月。建议观看 Point-Free 的视频并研究 TCA 的示例仓库。

相关博客文章

查看全部文章

相关项目

查看全部项目
全部对比