apple/TCA

[TCA] OptionalState (IfLetCase)

lgvv 2023. 9. 27. 02:49

[TCA] OptionalState (IfLetCase)

 

목차

 - OptionalState란?

 - OptionalState 예제

 - IfLetCase 알아보기

 

 

 - OptionalState란?

Reducer의 State 중 optional로 선언된 state를 일컬음.

 

 

 - OptionalState 예제

 1. optional값은 IfLetCase를 통해서 분기처리 가능

 

 - 자세한 사항을 코드의 주석 참고

 

// MARK: - Feature domain

struct OptionalBasics: Reducer {
    struct State: Equatable {
        var optionalCounter: Counter.State? // 1. ✅ State를 optional 상태로 보유
    }
    
    enum Action: Equatable {
        case optionalCounter(Counter.Action)
        case toggleCounterButtonTapped
    }
    
    var body: some Reducer<State, Action> {
        Reduce { state, action in
            switch action {
            case .toggleCounterButtonTapped:
                state.optionalCounter = state.optionalCounter == nil
                ? Counter.State()
                : nil
                return .none
            case .optionalCounter:
                return .none
            }
        }
        .ifLet(\.optionalCounter, action: /Action.optionalCounter) {
            // 2. ✅ Counter 리듀서를 OptionalBasics 리듀서와 통합
            Counter()
        }
    }
}

// MARK: - Feature view

struct OptionalBasicsView: View {
    let store: StoreOf<OptionalBasics>
    
    var body: some View {
        WithViewStore(self.store, observe: { $0 }) { viewStore in
            Form {
                Section {
                    AboutView(readMe: readMe)
                }
                
                Button("Toggle counter state") {
                    viewStore.send(.toggleCounterButtonTapped)
                }
                
                // 3. ✅ View에서 IfLetStore로 optional 값을 분기
                IfLetStore(
                    self.store.scope(
                        state: \.optionalCounter, // 3-1. ✅ optional state값 연결 (type: Counter.State?)
                        action: OptionalBasics.Action.optionalCounter // 3-2. ✅ action 연결 (type: Counter.Action)
                    ),
                    then: { store in // 3-3. ✅ store (type: store: Store<Counter.State, Counter.Action>)
                        Text(template: "`CounterState` is non-`nil`")
                        CounterView(store: store)
                            .buttonStyle(.borderless)
                            .frame(maxWidth: .infinity)
                    },
                    else: {
                        // 3-4. ✅ state가 nil인 경우
                        Text(template: "`CounterState` is `nil`")
                    }
                )
            }
        }
        .navigationTitle("Optional state")
    }
}

// MARK: - SwiftUI previews

struct OptionalBasicsView_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            NavigationView {
                OptionalBasicsView(
                    store: Store(initialState: OptionalBasics.State()) {
                        OptionalBasics()
                    }
                )
            }
            
            NavigationView {
                OptionalBasicsView(
                    store: Store(
                        initialState: OptionalBasics.State(optionalCounter: Counter.State(count: 42))
                    ) {
                        OptionalBasics()
                    }
                )
            }
        }
    }
}


// MARK: - Counter.swift

struct Counter: Reducer {
  struct State: Equatable {
    var count = 0
  }

  enum Action: Equatable {
    case decrementButtonTapped
    case incrementButtonTapped
  }

  func reduce(into state: inout State, action: Action) -> Effect<Action> {
    switch action {
    case .decrementButtonTapped:
      state.count -= 1
      return .none
    case .incrementButtonTapped:
      state.count += 1
      return .none
    }
  }
}

// MARK: - Feature view

struct CounterView: View {
  let store: StoreOf<Counter>

  var body: some View {
    WithViewStore(self.store, observe: { $0 }) { viewStore in
      HStack {
        Button {
          viewStore.send(.decrementButtonTapped)
        } label: {
          Image(systemName: "minus")
        }

        Text("\(viewStore.count)")
          .monospacedDigit()

        Button {
          viewStore.send(.incrementButtonTapped)
        } label: {
          Image(systemName: "plus")
        }
      }
    }
  }
}

 

 

 - IfLetCase 알아보기

 

optional state의 unwrap store를 nil과 아닌 값을 분리하여 두개의 뷰를 나누어 보여주는 View

 

정의

struct IfLetStore<State, Action, Content> where Content : View

 

예제

IfLetStore(
  store.scope(state: \.results, action: Search.Action.results) // 1. ✅ scope 지정
) {
  SearchResultsView(store: $0) // 2. ✅ then: nil이 아닌 상태의 뷰
} else: {
  Text("Loading search results...") // 2. ✅ else: nil 상태의 뷰
}

 

구체적 사용 예제

IfLetStore

 

 

 

 

 

 

 

(참고)

https://pointfreeco.github.io/swift-composable-architecture/1.0.0/documentation/composablearchitecture/ifletstore

 

Documentation

 

pointfreeco.github.io

https://github.com/pointfreeco/swift-composable-architecture/blob/main/Examples/CaseStudies/SwiftUICaseStudies/01-GettingStarted-OptionalState.swift

 

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

[TCA] Effect #1 (Basics)  (1) 2023.10.07
[TCA] SharedState  (1) 2023.09.27
[TCA] FocusState  (0) 2023.09.27
[TCA] Binding  (0) 2023.09.27
[TCA] Tutorial #5 (Multiple presentation destinations)  (0) 2023.09.24