Scope๋?
์ด์ ํํ ๋ฐฉ์
struct Scope<ParentState, ParentAction, Child> where Child : ReducerProtocol
ํ์ฌ ํํ ๋ฐฉ์
struct Scope<ParentState, ParentAction, Child: Reducer>: Reducer
- parent domain์ child domain์ผ๋ก ๋ณ๊ฒฝ -> ํ์ ๋๋ฉ์ธ์์ child reduce๋ฅผ ์คํ ์ํด
- ํฐ ๊ท๋ชจ์ feature์ ์์ ๋จ์๋ก ์ชผ๊ฐ๊ฑฐ๋ ํฉ์น ์ ์์
- ์์ ๋จ์์ feature๋ ํฐ ๊ท๋ชจ์ feature ๋ณด๋ค ์ดํดํ๊ธฐ ์ฌ์ฐ๋ฉฐ test ๋ฐ ๋ชจ๋ํ๋ ๋ฐฉ์์ผ๋ก ๊ด๋ฆฌํ ์ ์์
Scope ์์ฑ
@inlinable
public init<ChildState, ChildAction>(
state toChildState: WritableKeyPath<ParentState, ChildState>,
action toChildAction: AnyCasePath<ParentAction, ChildAction>,
@ReducerBuilder<ChildState, ChildAction> child: () -> Child
) where ChildState == Child.State, ChildAction == Child.Action {
self.init(
toChildState: .keyPath(toChildState),
toChildAction: toChildAction,
child: child()
)
}
- state
- parent state ๋ด๋ถ์ ์ ์๋ child state๋ฅผ ์๋ณํ๊ธฐ ์ํ writable key path
- action
- parent action ๋ด๋ถ์ ์ ์๋ child action์ ์๋ณํ๊ธฐ ์ํ case path
- @ReducerBuilder
- child domain์์ ์คํ๋๊ธฐ ์ํ๋ reducer๋ฅผ ๋๊ฒจ์ค๋ค
Scope(state: \.amountControl, action: /Action.amountControl) {
AmountControl()
._printChanges()
}
- ์์ ๋๋ฉ์ธ์ ์์ ๋๋ฉ์ธ์์ ์คํ๋์ด์ผ ํ๋ action์ ๊ฐ๋ก์ฑ์ ์์ reducer์๊ฒ ๊ณต๊ธ -> ๋ถ๋ชจ State ์ ๋ฐ์ดํธ, effects ์คํ
Scope๊ฐ ํ๋ ์ผ
- ์ ํ๋ฆฌ์ผ์ด์ ์ํ์ ์ก์ ์ ๊ด๋ฆฌ ๊ฐ๋ฅํ๊ณ ๋ชจ๋ํ๋ ๋ฐฉ์์ผ๋ก ๋๋์ด ๋ค๋ฃฐ ์ ์๊ฒ ํจ
- State Scoping
- State ์ค ์ผ๋ถ๋ฅผ child view์์ ์ฌ์ฉํ ์ ์๊ฒ ํจ
- child view๋ ํ์ํ ์ํ๋ง ๋ค๋ฃฐ ์ ์๊ฒ ๋จ
- ์ฝ๋ ๋ณต์ก์ฑ ์ค์ด๋ค๊ณ
(์ค์)๋ชจ๋ํ, ์ํ ๋ณ๊ฒฝ ์ฝ๊ฒ ์ถ์ ๊ฐ๋ฅ
- Action Scoping
- child view์์ ๋ฐ์ํ๋ ์ก์ ์ ์์ ๋ทฐ์ ์ก์ ์ผ๋ก ๋ณํ
- child view๋ ์์ฒด์ ์ธ ์ก์ ์ ์ ์ํ ์ ์์
- child view ์ก์ ์ ์์ ์ก์ ์ผ๋ก ๋ณํ๋์ด ์ํธ์์ฉ
* ์์ ์ฝ๋์์ ProductDetail~ ์ ์์, AmountControl~์ ํ์
Child Reducer ์์ฑ
struct AmountControl: Reducer {
struct State: Equatable {
var amount: Int = 0
}
enum Action: Equatable {
case minusButtonTapped
case plusButtonTapped
}
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .minusButtonTapped:
state.amount -= 1
return .none
case .plusButtonTapped:
state.amount += 1
return .none
}
}
}
}
- Child View์ธ AmountControlView์์ ์ฌ์ฉ๋ Reducer ์ ์์ฑ
- ํ์ ๋ทฐ์์ ์ฌ์ฉํ State์ Action ์ ์
Parent Domain์ Child Domain ์ ์
struct ProductDetailFeature: Reducer {
struct State: Equatable {
var amountControl = AmountControl.State()
}
enum Action: Equatable {
case amountControl(AmountControl.Action)
}
- ์์ State๋ฅผ ๋ถ๋ชจ State์, ์์ Action์ ๋ถ๋ชจ Action์ ํฌํจ ์ํด
var body: some ReducerOf<Self> {
Scope(state: \.amountControl, action: /Action.amountControl) {
AmountControl()
._printChanges()
}
// ... ์๋ต
- Parent reducer์ child domain์ ํฌํจ ์์ผ child reducer๋ฅผ ์๋ฒ ๋๋ ์ํด
- body ๋ด๋ถ์ ์์๋ Child Reducer ์์ฑ ํ Parent Reducer (๋ถ๋ชจ ๋ทฐ์์ ๋ชจ๋ ์ฒ๋ฆฌํ ๋ค ์์ ๋ทฐ์ ์ด๋ฒคํธ๋ฅผ ์ ๋ฌํ์ง ์์ ์ ์๊ธฐ ๋๋ฌธ์)
Enum state
- Scope reducer ๋ํ struct๊ฐ ์๋ enum
case .amountControl(let childAction):
// ์์ State์ ๊ฐ์ ๋ณํ ๋ฟ๋ง ์๋๋ผ ์นํ๋ ๊ฐ๋ฅ
switch childAction {
case .minusButtonTapped:
state.productAmount -= 1
return .none
case .plusButtonTapped:
state.productAmount += 1
return .none
}
}
- parent์ action์ธ amountControl ๋ด์์ child action์ enum์ผ๋ก ๊ตฌ์กฐํ ํ ์ ์์
Child View init
AmountControlView(
store: self.store.scope(state: \.amountControl, action:
ProductDetailFeature.Action.amountControl)
)
- store ์ ๋ฌ ์ Parent View์ store์ scope ๋ฉ์๋๋ก ์ ๋ฌ
struct AmountControlView: View {
let store: StoreOf<AmountControl>
var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
// ์๋ต
์ฐธ๊ณ ์ฌ์ดํธ
Documentation
pointfreeco.github.io
'iOS ๐ > Architecture' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[TCA] Overriding Dependencies (feat. Dependency Injection) (0) | 2023.12.13 |
---|---|
[SwiftUI/TCA] Binding (0) | 2023.12.07 |
[SwiftUI/TCA] Store and ViewStore (0) | 2023.11.29 |
[SwiftUI/TCA] Effect ๊ตฌํ๊ณผ ํ์ฉ (0) | 2023.11.29 |
[SwiftUI/TCA] Timer ๋ง๋ค๊ธฐ (1) | 2023.11.24 |