Open2
TCA 覚書

FlutterのBlocとかriverpodとか照らし合わせながら違いを見つつ、TCAを学んでいくスタイル

Reducer
BlocだとこれがTCAでいうReducer相当
class QuestBloc extends Bloc<QuestEvent, QuestState> {
final _questUseCase = QuestUseCase();
QuestBloc()
: super(
Loading(
QuestList[],
Quest(
),
),
) {
on<AddQuestButtonTapped>((event, emit) async {
});
on<Binding>((event, emit) async {
});
on<ClearCompletedButtonTapped>((event, emit) async {
});
on<Delete>((event, emit) async {
});
on<Move>((event, emit) async {
});
}
}
TCAだと var bodyがBlocのon<Hoge Huga>に相当するものっぽい
struct QuestListFeature: Reducer {
struct State: Equatable {}
enum Action: BindableAction, Equatable, Sendable {}
var body: some Reducer<State, Action> {
BindingReducer()
Reduce { state, action in
switch action {
case .addQuestButtonTapped:
state.quests.insert(QuestFeature.State(id: self.uuid()), at: 0)
return .none
case .binding:
return .none
case .clearCompletedButtonTapped:
state.quests.removeAll(where: \.isCompleted)
return .none
case let .delete(indexSet):
let filteredQuests = state.filteredQuests
for index in indexSet {
state.quests.remove(id: filteredQuests[index].id)
}
return .none
case var .move(source, destination):
if state.filter == .completed {
source = IndexSet(
source
.map { state.filteredQuests[$0] }
.compactMap { state.quests.index(id: $0.id) }
)
destination =
(destination < state.filteredQuests.endIndex
? state.quests.index(id: state.filteredQuests[destination].id)
: state.quests.endIndex)
?? destination
}
state.quests.move(fromOffsets: source, toOffset: destination)
return .run { send in
try await self.clock.sleep(for: .milliseconds(100))
await send(.sortCompletedQuests)
}
case .sortCompletedQuests:
state.quests.sort { $1.isCompleted && !$0.isCompleted }
return .none
case .quest(id: _, action: .binding(\.$isCompleted)):
return .run { send in
try await self.clock.sleep(for: .seconds(1))
await send(.sortCompletedQuests, animation: .default)
}
.cancellable(id: CancelID.questCompletion, cancelInFlight: true)
case .quest:
return .none
}
}
.forEach(\.quests, action: /Action.quest(id:action:)) {
QuestFeature()
}
}
}
Action
Blocだと
part of 'user_bloc.dart';
class QuestEvent with _$QuestEvent {
const factory QuestEvent.addQuestButtonTapped() = AddQuestButtonTapped;
const factory QuestEvent.binding() = Binding;
const factory UserEvent.clearCompletedButtonTapped() = ClearCompletedButtonTapped;
const factory QuestEvent.delete() = Delete;
const factory QuestEvent.move() = Move;
const factory QuestEvent.sortCompletedQuests() = SortCompletedQuests;
const factory QuestEvent.quest() = Quest;
}
TCAも割と似たような感じ
enum Action: BindableAction, Equatable, Sendable {
case addQuestButtonTapped
case binding(BindingAction<State>)
case clearCompletedButtonTapped
case delete(IndexSet)
case move(IndexSet, Int)
case sortCompletedQuests
case quest(id: QuestFeature.State.ID, action: QuestFeature.Action)
}
State
Blocだと
class QuestState with _$QuestState {
const factory UserState.loading(
List<Quest> questList, List<Quest>) =
Loading;
const factory QuestState.loaded(
List<Quest> questList, List<Quest> ) =
Loaded;
}
TCAだとStateごとに切り分けとかってわけではなく
一個一個のプロパティの状態を管理するみたいなやり方っぽい
struct State: Equatable {
@BindingState var editMode: EditMode = .inactive
@BindingState var filter: Filter = .all
var quests: IdentifiedArrayOf<QuestFeature.State> = []
var filteredQuests: IdentifiedArrayOf<QuestFeature.State> {
switch filter {
case .active: return self.quests.filter { !$0.isCompleted }
case .all: return self.quests
case .completed: return self.quests.filter(\.isCompleted)
}
}
}
ViewとBindingさせるやり方
Bloc
TCA