Store
애플리케이션을 실제로 구동하는 런타임 객체입니다. 뷰는 Store를 통해 기능의 상태를 읽고, 사용자의 동작을 액션으로 보냅니다.
원문: Store
Store<State, Action>는 현재 상태와 reducer를 연결합니다. 액션을 받으면 reducer를 실행해 상태를 갱신하고, reducer가 반환한 Effect도 실행합니다. 따라서 뷰는 reducer를 직접 호출하거나 상태를 직접 변경하지 않고, Store 하나만 의존하면 됩니다.
선언
Store는 메인 액터에서 동작합니다. 또한 동적 멤버 조회를 지원하므로, 관찰 가능한 기능 상태의 속성을 store.count처럼 Store에서 바로 읽을 수 있습니다.
루트 Store 만들기
보통 앱의 진입점에서 루트 Store를 하나 만들고, 최상위 뷰에 전달합니다.
initialState는 앱이 시작할 때 사용할 상태이고, reducer는 이 Store의 비즈니스 로직입니다. withDependencies 인자로 reducer가 접근할 의존성을 이 Store에 한해 재정의할 수도 있습니다.
참고: Xcode Preview는 앱 진입점을 생성해 실행합니다. 루트 Store를 일반 인스턴스 속성으로 만들면 Preview를 열 때 의존성이 예상보다 일찍 실행될 수 있습니다. 원문 권장처럼
static let으로 루트 Store를 보관하세요.
상태 읽기와 액션 보내기
기능의 상태에 @ObservableState를 적용했다면, 뷰는 Store에서 상태를 읽고 사용자 이벤트에 맞춰 액션을 보냅니다.
send(_:)는 액션을 Store로 보냅니다. Store는 reducer를 실행한 뒤 반환된 effect의 생명주기를 나타내는 StoreTask를 돌려줍니다.
SwiftUI의 task와 연결하면 화면의 비동기 작업을 화면 생명주기에 맞춰 기다리거나 취소할 수 있습니다.
StoreTask는 다음 기능을 제공합니다.
send(_:animation:)과 send(_:transaction:) 오버로드도 있지만 사용 중단 예정입니다. 애니메이션이나 트랜잭션이 필요하면 withAnimation 또는 withTransaction으로 send 호출을 감싸세요.
Store 스코핑
scope(_:action:)은 전체 도메인을 다루는 Store에서 자식 기능의 상태와 액션만 다루는 Store를 만듭니다. 하위 뷰가 앱 전체 상태나 액션을 알 필요 없게 하므로, 기능을 독립적인 모듈로 나누는 데 중요합니다.
루트 Store를 각 탭의 Store로 스코핑해 전달할 수 있습니다.
첫 번째 인자는 부모 상태에서 자식 상태로 가는 키 경로이고, 두 번째 인자는 자식 액션을 부모 액션으로 감싸는 케이스 키 경로입니다. 스코핑한 Store에서 보낸 자식 액션은 부모 Store와 reducer로 전달되며, 자식 Store의 상태는 부모 상태에서 파생됩니다.
상태 관찰
Store는 ObservableObject를 준수하지만 @ObservedObject로 관찰하는 용도가 아닙니다. 이 준수는 Store를 SwiftUI의 @StateObject에 보관할 수 있게 하기 위한 것입니다.
현재 TCA에서는 Swift Observation을 사용합니다. 기능 상태에 @ObservableState를 적용하고, iOS 17 미만을 지원한다면 Perception 패키지를 함께 사용하세요. Combine 기반의 store.publisher는 사용 중단 예정이므로 새 코드에서는 Observation을 사용합니다.
타입 별칭
StoreOf
StoreOf는 reducer의 State와 Action을 풀어 쓴 Store 타입의 편의 별칭입니다.
아래 두 선언은 같습니다.
기능의 Store를 선언할 때는 보통 더 짧고, reducer와의 관계가 분명한 StoreOf<Feature>를 사용합니다.