apple/TCA

[TCA] HigherOrderReducers #1 (Recursion)

lgvv 2023. 10. 11. 01:23

[TCA] HigherOrderReducers #1 (Recursion)

 

목차

 - 이번 주제에 대한 간략 설명

 - 예제 코드

 

# 이번 주제에 대한 간략 설명

이번에는 재귀를 통해 View를 관리 하는 방법을 알아볼 예정

 

 

# 예제 코드

주요하게 살펴볼 부분

1. State를 rows로 다시 들고 있음으로써 Nested 형태를 구성

2. Action에는 간접 참조를 위해 indirect enum으로 선언

3. Reducer에 forEach(...) { Self() } 형태로 참조

 

struct Nested: Reducer {
    struct State: Equatable, Identifiable {
        let id: UUID
        var name: String = ""
        var rows: IdentifiedArrayOf<State> = []
    }
    
    enum Action: Equatable {
        case addRowButtonTapped
        case nameTextFieldChanged(String)
        case onDelete(IndexSet)
        indirect case row(id: State.ID, action: Action)
    }
    
    @Dependency(\.uuid) var uuid
    
    var body: some Reducer<State, Action> {
        Reduce { state, action in
            switch action {
            case .addRowButtonTapped:
                state.rows.append(State(id: self.uuid()))
                return .none
                
            case let .nameTextFieldChanged(name):
                state.name = name
                return .none
                
            case let .onDelete(indexSet):
                state.rows.remove(atOffsets: indexSet)
                return .none
                
            case .row:
                return .none
            }
        }
        .forEach(\.rows, action: /Action.row(id:action:)) {
            Self() // ✅ forEach 부분에 Self() 연결
        }
    }
}

// MARK: - Feature view

struct NestedView: View {
    let store: StoreOf<Nested>
    
    var body: some View {
        WithViewStore(self.store, observe: \.name) { viewStore in
            Form {
                Section {
                    AboutView(readMe: readMe)
                }
                
                // ✅ ForEachStore을 사용하여 배열을 처리!!
                ForEachStore(
                    self.store.scope(state: \.rows, action: Nested.Action.row(id:action:))
                ) { rowStore in
                    WithViewStore(rowStore, observe: \.name) { rowViewStore in
                        NavigationLink(
                            destination: NestedView(store: rowStore)
                        ) {
                            HStack {
                                TextField(
                                    "Untitled",
                                    text: rowViewStore.binding(send: Nested.Action.nameTextFieldChanged)
                                )
                                Text("Next")
                                    .font(.callout)
                                    .foregroundStyle(.secondary)
                            }
                        }
                    }
                }
                .onDelete { viewStore.send(.onDelete($0)) }
            }
            .navigationTitle(viewStore.state.isEmpty ? "Untitled" : viewStore.state)
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button("Add row") { viewStore.send(.addRowButtonTapped) }
                }
            }
        }
    }
}

extension Nested.State {
    static let mock = Nested.State(
        id: UUID(),
        name: "Foo",
        rows: [
            Nested.State(
                id: UUID(),
                name: "Bar",
                rows: [
                    Nested.State(id: UUID(), name: "", rows: [])
                ]
            ),
            Nested.State(
                id: UUID(),
                name: "Baz",
                rows: [
                    Nested.State(id: UUID(), name: "Fizz", rows: []),
                    Nested.State(id: UUID(), name: "Buzz", rows: []),
                ]
            ),
            Nested.State(id: UUID(), name: "", rows: []),
        ]
    )
}

// MARK: - SwiftUI previews

struct NestedView_Previews: PreviewProvider {
    static var previews: some View {
        NavigationView {
            NestedView(
                store: Store(initialState: .mock) {
                    Nested()
                }
            )
        }
    }
}

 

 

'apple > TCA' 카테고리의 다른 글

[TCA] HigherOrderReducers #2 (ReusableFavoriting)  (1) 2023.10.29
[TCA] Navigation (화면전환 총 정리)  (0) 2023.10.09
[TCA] Effect #6 (WebSocket)  (0) 2023.10.08
[TCA] Effect #5 (Timers)  (0) 2023.10.07
[TCA] Effect #4 (Refreshable)  (0) 2023.10.07