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
// ์ƒ๋žต

์ฐธ๊ณ  ์‚ฌ์ดํŠธ

https://pointfreeco.github.io/swift-composable-architecture/0.54.0/documentation/composablearchitecture/scope/

 

Documentation

 

pointfreeco.github.io

 

Pixabay ๋กœ๋ถ€ํ„ฐ ์ž…์ˆ˜๋œ&nbsp; PublicDomainPictures ๋‹˜์˜ ์ด๋ฏธ์ง€

'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