Open2

TCA 覚書

alexander_steelalexander_steel

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

alexander_steelalexander_steel

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