大元:TCAとMVVMの違いがわからんかった。
勘違いその1
Q:@ObservableStateとはなにを宣言するものですか?これは双方向バインディングとかかわりがありますか?(サンプルのStateに掛かってた)
A:@ObservableStateは、特定の状態(State)を宣言し、それが変更された場合にUIや他の依存する部分が自動的に更新される仕組みを提供する注釈やデコレーターの一種です。特に、リアクティブプログラミングをサポートするフレームワークやライブラリで使われることがあります。
基本的な役割:
状態の追跡:
@ObservableStateでラベル付けされた変数は、特定の「監視可能な」状態として扱われます。
この状態が変化すると、それに依存する部分(通常はUIコンポーネントや他のロジック)が通知を受けて再レンダリングや更新を行います。
リアクティブなデータフローの実現:
明示的にイベントをトリガーするのではなく、状態の変更に応じて自動的に依存関係が解決されます。
*要はレンダリングであってバインディングではなかった。
ここを間違えてたから単一方向データフローって言われても納得出来てなかった。
Q:TCAではMVVMのViewModelが肥大になる問題と同様にReduce部が肥大になるのでは?
A:概ねその通り。
Q:MVVMとTCAを両方採用することは可能ですか?
A:
MVVM と TCA を組み合わせる方法
このアプローチでは、MVVM が Presentation 層の責務を、TCA が Domain 層の責務をカバーする形になります。
- Presentation 層: MVVM
View: UI を構築し、ユーザー入力を ViewModel に伝達。
ViewModel: ドメイン層(TCA の Store や Reducer)を橋渡しする役割。
ViewModel は TCA の ViewStore を内部に保持し、TCA の状態やアクションを簡潔に扱う。 - Domain 層: TCA
State: ドメインに関する状態をすべて保持。
Action: UI からの操作や非同期処理の結果を受け付ける。
Reducer: アクションに応じて状態を変更し、必要に応じてエフェクトを生成。
Q:ドメイン層ってビジネスロジック(各種通信やデータベースなどから取得した情報を保持する所)を書く所だよね?なぜドメイン層にActionが存在していいのかわからないのだけど説明して(Actionがある以上Viewと密接な存在になると思っています)
A:良い質問です!ドメイン層に Action が存在する理由を説明するには、「Action の役割」と「TCA における責務の分離」 を理解することがポイントです。
- ドメイン層における Action の役割
Action は 「状態(State)をどのように変化させるかを定義するトリガー」 です。
TCA のようなアーキテクチャでは、以下の役割分担があります:
State: アプリケーションの現在の状態(ドメイン層の中心)。
Action: 状態を変化させるための意図を表現(「ユーザーがボタンを押した」や「非同期処理が完了した」など)。
Reducer: Action を受け取り、状態をどのように更新するかを定義(ビジネスロジックが書かれる場所)。
これを踏まえると、Action は「View」からの単なる命令ではなく、ドメイン層の状態管理を一貫して扱うための中心的な概念として扱われます。
- ドメイン層と Action の関係
Action がドメイン層に存在するのは、「アプリケーションの状態管理をドメイン層で完結させる」 ためです。
View と Action の分離
View は単に store.send() を呼び出して、アクションをトリガーします。
具体的な状態変更や副作用(API 呼び出し、データベース操作)は、View ではなく Reducer や Environment(ドメイン層の一部)で処理されます。
つまり、Action は View の一部ではなく、ドメイン層の状態を管理するための操作の定義という位置づけです。
- TCA の設計思想
TCA では、UI 層とドメイン層の責務を明確に分離するために、Action をドメイン層に配置しています。
状態管理を一元化:
View はドメイン層に状態管理を委ねます。
これにより、状態変更がすべて Reducer を通じて行われ、一貫性が確保されます。
Action をドメイン層で扱うメリット:
ドメイン層に Action を置くことで、View からのイベントも非同期処理も、統一的な方法で管理できます。
たとえば、非同期 API 呼び出しの結果(.dataFetched(Result))なども Action として扱えるため、状態遷移の全体像を明確に保てます。
4. ドメイン層に Action を置く具体的な利点
例: Todo リストの管理
アプリケーションに「タスクを追加する」機能があるとします。
State: 現在のタスクリスト。
Action: タスク追加操作の意図を表現。
Reducer: タスクをリストに追加するロジック。
ドメイン層の例
struct TodoState: Equatable {
var todos: [String] = []
}
enum TodoAction: Equatable {
case addTodo(String)
}
struct TodoEnvironment {}
let todoReducer = Reducer<TodoState, TodoAction, TodoEnvironment> { state, action, _ in
switch action {
case .addTodo(let newTodo):
state.todos.append(newTodo)
return .none
}
}
Viewでの使い方
struct TodoView: View {
let store: Store<TodoState, TodoAction>
var body: some View {
WithViewStore(store) { viewStore in
VStack {
List(viewStore.todos, id: \.self) { todo in
Text(todo)
}
Button("Add Todo") {
viewStore.send(.addTodo("New Task"))
}
}
}
}
}
ここでのポイント:
View は 「タスクを追加したい」 という意図 (viewStore.send(.addTodo)) を伝えるだけ。
実際のタスク追加ロジック(state.todos.append)は Reducer 内に閉じ込められ、ドメイン層で一元管理されます。
5. なぜ View と密接な存在ではないのか?
Action が View と密接な存在に見えるのは、Action が View のイベントをトリガーするために使われるからです。しかし、以下の理由から密接ではありません:
Action は View からのイベントだけではない:
非同期処理の完了、データベース操作の結果、タイマーイベントなども Action に含まれます。
これにより、状態遷移が UI イベントだけに依存しなくなります。
Action は状態変更のための定義:
Action は「状態をどう変化させるか」を定義するもので、直接 UI 操作を行うものではありません。