변경을 처리한다: Reducer
Reducer 는 애플리케이션의 현재 State 를 주어진 Action 을 바탕으로 어떻게 다음 State 로 바꿀 것이지를 묘사하고,
어떤 결과(Effect )가 존재한다면 Store 를 통해 어떻게 실행되어야 하는지를 설명하는 프로토콜이다.
애플리케이션의 현재 상태(State )를 함수형으로, 가독성이 좋게 작성하기 위해 고안된 개념이다.
→ 클라이언트의 입장에서 사용자의 상호작용에 따라 상태를 변형할 수 있도록 돕는다
TCA에서 사용자가 뷰에서 Action 을 취하면 Action 은 Reducer 를 통해 State 를 변화시키는 방식으로 작동한다.
Reducer는 아래와 같이, 프로토콜을 채택하는 형태로 이루어져 있다.
// Reducer
struct Feature: Reducer {
struct State: Equatable {
var count = 0
}
enum Action: Equatable {
case decrementButtonTapped
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .decrementButtonTapped:
state.count -= 1
return .none
}
}
}
// OR
func reduce(into state: inout State, action: Action) -> Effect<Action> {
switch action {
case .decrementButtonTapped:
state.count -= 1
return .none
}
}
//
}
Swift
복사
구조체에 Reducer 프로토콜을 채택하게 되면 State 와 Action 를 만들도록 자동완성이 된다.
Reducer 프로토콜의 내부코드
public protocol Reducer<State, Action> {
/* code */
func reduce(into state: inout State, action: Action) -> Effect<Action>
@ReducerBuilder<State, Action>
var body: Body { get }
}
Swift
복사
Reducer 를 구현하는 방법에는 두 가지 방법이 있다.
1. reduce(into: action: )
•
이 방법은 더 기본적인 방법을, reducer의 로직을 직접 reduce(into: action: ) 메서드 내에서 구현하는 방식이다.
•
다른 reducer와의 결합이 필요없는 경우에 추천되는 방법!
•
reducer의 로직을 직접 작성하는데 중점
2. body
•
더 고수준적인 방법으로, body 속성 내에서 직접 상태 변경 또는 효과 로직을 수행하지 않고, 여러 다른 reducer를 조합하는 방식으로 주로 사용된다.
•
해당 reducer가 추후에 더 작은 단위로 나눠진다면 이 방법을 사용하는 쪽이 더 편리하다.
•
더 높은 수준에서 reducer를 조합하고 기능을 구성하는데 중점
•
body 로 사용하게 될 경우, 연산 프로퍼티로 some Reducer 로 사용하게 될텐데, 이는 메서드와 달리 연산 프로퍼티로서 불투명 타입(Opaque Type)으로 방출할 수 있게 된다.
◦
이 말은, 연산 프로퍼티로 만든 리듀서들끼리 조합이 가능해진다는 말!
◦
따라서, 앱의 복잡도가 증가했을 때 더욱 이점이 있다.
Dependency
Reducer 안에 ‘의존성’을 가리키는 Dependency 가 생기는 경우는 어떻게 처리해야할까?
ex) TCA에서 기본적으로 지원하는 Timer의 Dependency
@Dependency(\.continuousClock) var clock
Swift
복사
이렇게 Dependency 로 의존성을 주입하게 되면, Reducer 안에서 이 의존성을 활용하여, 더욱 복잡한 형태로 우리가 원하는 데이터를 편집하여 Effect 로 방출이 될 것이다.
sturct CounterFeature: Reducer {
@Dependency(\.continuousClock) var clock
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .toggleTimerButtonTapped:
state.isTimerOn.toggle()
if state.isTimerOn {
return .run { send in
// 주입된 의존성 활용
for await _ in self.clock.timer(interval: .seconds(1)) {
await send(.timerTicked)
}
}
.cancellale(id: CancelID.timer)
}
else {
// Stop the timer
return .cancel(id: CancelID.timer)
}
}
}
}
}
Swift
복사