在 SwiftUI 時代的架構討論中,有一個名字總是無法避開:Point-Free 打造的 TCA(The Composable Architecture)。
如果前幾篇介紹的 MVVM(Model-View-ViewModel)與 Clean Architecture 討論的是「該如何劃分職責」,那麼 TCA 提出的問題就不同了。「能不能用一套規則控制狀態變更的所有路徑?」
今天就來整理 TCA 的單向資料流如何運作,以及我們能得到什麼、又要付出什麼代價。
三種材料:State、Action、Reducer
在 TCA 中,一個畫面(或一項功能)由三個部分定義。
- State:保存這項功能所有狀態的單一結構
- Action:包含這項功能可能發生之所有事件的 enum,從按鈕點擊、回應到達,到收到通知,全都包含在內
- Reducer:定義「目前的 State 收到這個 Action 時,State 會如何變更」的純函式
@Reducer
struct Counter {
@ObservableState
struct State: Equatable {
var count = 0
}
enum Action {
case incrementTapped
case decrementTapped
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .incrementTapped:
state.count += 1
return .none
case .decrementTapped:
state.count -= 1
return .none
}
}
}
}
View 無法直接變更狀態,能做的只有向 Store 傳送 Action。Store 執行 Reducer 並變更 State 後,View 便會繪製更新後的 State。
사건 발생 → Action 전송 → Reducer가 State 변경 → View 갱신
這個單向循環就是全部。狀態沒有其他偷偷變更的路徑。「這個值到底在哪裡變的?」這種除錯迷宮會從結構上消失。
連副作用也納入規則
網路請求等非同步工作不由 Reducer 直接執行,而是以 Effect 回傳。Effect 完成後,結果會再次以 Action 的形式回來。
case .refreshTapped:
state.isLoading = true
return .run { send in
let posts = try await postClient.fetch()
await send(.postsLoaded(posts))
}
case .postsLoaded(let posts):
state.isLoading = false
state.posts = posts
return .none
postClient 等相依性會透過 TCA 的相依性注入系統插入,因此測試時可以替換成假的 client。TCA 最強大的武器就在這裡出現了:TestStore。
let store = TestStore(initialState: Feed.State()) { Feed() }
await store.send(.refreshTapped) { $0.isLoading = true }
await store.receive(.postsLoaded(mockPosts)) {
$0.isLoading = false
$0.posts = mockPosts
}
它會強制你完整明確定義每個 Action 對 State 應造成的變更。只要出現一個未預期的狀態變更,測試就會失敗。這種程度的完整性驗證,很難用 MVVM 測試來模仿。
代價是什麼?
這份精準度也伴隨著帳單。
**學習曲線很陡。**在開始產生生產力之前,必須先學會 Reducer、Effect、Store、Scope 與相依性系統等許多概念。對沒有函數式程式設計背景的團隊而言更是如此。
**存在樣板程式碼。**即使只改一個值,也得建立 Action case,並在 Reducer 中新增分支。對簡單畫面來說,這種儀式感過於繁重。
**它是第三方相依套件。**TCA 並非由 Apple,而是由 Point-Free 打造的函式庫。為了配合每年變動的 SwiftUI,TCA 也經歷過多次大型遷移,應用程式的所有畫面最後都會建立在這個函式庫之上。它是受到積極維護且成熟的專案,但決定把應用程式的骨架交給外部套件,並不是一個可以輕率做出的決定。
它適合什麼樣的團隊?
TCA 能發揮價值的條件相當明確。
- 狀態錯綜複雜的應用程式,例如協作文件、金融、複雜表單與即時同步
- 狀態變更的完整測試十分重要的領域
- 熟悉函數式概念,或能投入學習成本的團隊
相反地,如果應用程式大多數畫面只是「接收後顯示」,那麼第 3 篇介紹的 MV(Model-View)/MVVM 加上 UseCase,通常就已經足夠。TCA 不是錯誤的選擇,而是昂貴的選擇;只有當狀態複雜度足以合理化這項成本時,它才會真正發揮價值。
總結
- TCA 以 State、Action、Reducer 定義功能,並強制所有狀態變更只能沿著單一單向循環進行。
- 副作用也會透過 Effect → Action 納入規則,TestStore 則驗證狀態變更的完整性。
- 代價是學習曲線、樣板程式碼,以及把應用程式骨架交給第三方的決定。
- 這是一種取捨明確的架構:應用程式的狀態越複雜,TCA 帶來的效益就越大。
系列只剩最後一個問題。從 MVC 到 TCA,在這麼多選項中,**我們的團隊該選哪一個?**下一篇將整理選擇標準,為系列畫下句點。

![[iOS 架構 #7] TCA 與單向資料流 封面圖](/assets/images/posts/caac9fa2-c44c-4646-87b0-01421048db10/1.jpg)