Open Jinsujin opened 1 year ago
+, - λ²νΌμΌλ‘ μ«μλ₯Ό μ¦κ°&κ°μ μν€λ μ±
μ΄ κΈ°λ₯μ ꡬννλ €λ©΄ ReducerProtocol λ₯Ό μ€μνμ¬ κΈ°λ₯μ λλ©μΈκ³Ό λμμ μ μν μλ‘μ΄ type μ μμ±ν΄μΌ νλ€:
import ComposableArchitecture
struct Feature: ReducerProtocol {
}
μ¬κΈ°μ νμ¬ μΉ΄μ΄νΈμ λν integer μ, νλ©΄μ νμν alert μ title μ λνλ΄λ optional string λ‘ κ΅¬μ±λ κΈ°λ₯μ state type μ μ μν΄μΌ νλ€. (nil
μ alert μ νμνμ§ μμμ λνλ΄κΈ° λλ¬Έ)
struct Feature: ReducerProtocol {
struct State: Equatable {
var count = 0
var numberFactAlert: String?
}
}
λν κΈ°λ₯μ action μ λν type μ μ μν΄μΌ νλ€. μ¬κΈ°μλ κ°μ λ²νΌ, μ¦κ° λ²νΌ, fact λ²νΌμ ννμλμ κ°μ λͺ λ°±ν action μ΄ μλ€.
κ·Έλ¬λ μ¬κΈ°μλ μ½κ° λͺ ννμ§ μμ μ‘μ λ€λ μλ€. alert μ dismiss νκ±°λ fact API λ‘ λΆν° μλ΅μ λ°μλ λ°μνλ μμ μ²λΌ:
struct Feature: ReducerProtocol {
struct State: Equatable { β¦ }
enum Action: Equatable {
case factAlertDismissed
case decrementButtonTapped
case incrementButtonTapped
case numberFactButtonTapped
**case numberFactResponse(TaskResult<String>)**
}
}
κ·Έλ°λ€μ reduce
method λ₯Ό ꡬννλ€.
μ΄λ κΈ°λ₯(feature)μ λν μ€μ logic λ° λμμ μ²λ¦¬νλ μν μ νλ€.
νμ¬ state λ₯Ό next state λ‘ λ°κΎΈλ λ°©λ²κ³Ό μ΄λ€ effect λ₯Ό μ€νν΄μΌ νλμ§ μ€λͺ
νλ€. μΌλΆ actionμ effectλ₯Ό μ€νν νμκ° μμΌλ©°, μ΄λ₯Ό νννκΈ° μν΄ .none
μ λ°ννλ€:
struct Feature: ReducerProtocol {
struct State: Equatable { β¦ }
enum Action: Equatable { β¦ }
func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
switch action {
case .factAlertDismissed:
state.numberFactAlert = nil
return .none
case .decrementButtonTapped:
state.count -= 1
return .none
case .incrementButtonTapped:
state.count += 1
return .none
case .numberFactButtonTapped:
return .task { [count = state.count] in
await .numberFactResponse(
TaskResult {
String(
decoding: try await URLSession.shared
.data(from: URL(string: "http://numbersapi.com/\(count)/trivia")!).0,
as: UTF8.self
)
}
)
}
case let .numberFactResponse(.success(fact)):
state.numberFactAlert = fact
return .none
case .numberFactResponse(.failure):
state.numberFactAlert = "Could not load a number fact :("
return .none
}
}
}
κ·Έλ¦¬κ³ λμ λ§μ§λ§μΌλ‘ feature λ₯Ό νλ©΄μ νμνλ view λ₯Ό μ μνλ€.
stateμ λν λͺ¨λ λ³κ²½μ κ΄μ°°(observe) νκ³ λ€μ λ λλ§ν μ μλλ‘ StoreOf<Feature>
μ μ μ§νλ€. κ·Έλ¦¬κ³ μ°λ¦¬λ state κ° λ³κ²½λλλ‘ λͺ¨λ μ¬μ©μ action μ store λ‘ λ³΄λΌμ μλ€.
.alert
Β view modifier μ νμνIdentifiable
λ‘ λ§λ€κΈ° μν΄ fact alert μ£Όμμ struct wrapper λ₯Ό λμ
ν΄μΌ νλ€:
struct FeatureView: View {
let store: StoreOf<Feature>
var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
VStack {
HStack {
Button("β") { viewStore.send(.decrementButtonTapped) }
Text("\(viewStore.count)")
Button("+") { viewStore.send(.incrementButtonTapped) }
}
Button("Number fact") { viewStore.send(.numberFactButtonTapped) }
}
.alert(
item: viewStore.binding(
get: { $0.numberFactAlert.map(FactAlert.init(title:)) },
send: .factAlertDismissed
),
content: { Alert(title: Text($0.title)) }
)
}
}
}
struct FactAlert: Identifiable {
var title: String
var id: String { self.title }
}
λν UIKit controller λ₯Ό ꡬλ(driven)νλ κ²λ κ°λ¨νλ€.
UI λ₯Ό μ
λ°μ΄νΈνκ³ alert μ νμνκΈ° μν΄ viewDidLoad
μμ store λ₯Ό ꡬλ
(subscribe) νλ€. ν΄λΉ μ½λλSwiftUI λ²μ λ³΄λ€ λ κΈΈλ€:
class FeatureViewController: UIViewController {
let viewStore: ViewStoreOf<Feature>
var cancellables: Set<AnyCancellable> = []
init(store: StoreOf<Feature>) {
self.viewStore = ViewStore(store)
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
let countLabel = UILabel()
let incrementButton = UIButton()
let decrementButton = UIButton()
let factButton = UIButton()
// Omitted: Add subviews and set up constraints...
self.viewStore.publisher
.map { "\($0.count)" }
.assign(to: \.text, on: countLabel)
.store(in: &self.cancellables)
self.viewStore.publisher.numberFactAlert
.sink { [weak self] numberFactAlert in
let alertController = UIAlertController(
title: numberFactAlert, message: nil, preferredStyle: .alert
)
alertController.addAction(
UIAlertAction(
title: "Ok",
style: .default,
handler: { _ in self?.viewStore.send(.factAlertDismissed) }
)
)
self?.present(alertController, animated: true, completion: nil)
}
.store(in: &self.cancellables)
}
@objc private func incrementButtonTapped() {
self.viewStore.send(.incrementButtonTapped)
}
@objc private func decrementButtonTapped() {
self.viewStore.send(.decrementButtonTapped)
}
@objc private func factButtonTapped() {
self.viewStore.send(.numberFactButtonTapped)
}
}
μ±μ μ§μ μ (entry point)μμ μ΄ viewλ₯Ό νλ©΄μ νμν μ€λΉκ° λλ©΄, μ°λ¦¬λ storeλ₯Ό ꡬμ±(construct) ν μ μλ€. μ ν리μΌμ΄μ μ initial state μ, μ ν리μΌμ΄μ μ power λ₯Ό 곡κΈν reducer λ₯Ό μ§μ ν΄ μνν μ μλ€:
import ComposableArchitecture
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
FeatureView(
store: Store(
initialState: Feature.State(),
reducer: Feature()
)
)
}
}
}
μ΄κ±Έλ‘ νλ©΄μμ μ€ννκΈ° μΆ©λΆνλ€.
μμ SwiftUI λ°©μμΌλ‘ μ΄ μμ μ μννλ κ²½μ°λ³΄λ€ λͺλ¨κ³κ° λ νμνμ§λ§, λͺκ°μ§ μ΄μ μ΄ μλ€.
medium
λμ μ 리: TCA λ§λ³΄κΈ°What is the Composable Architecture?
μ΄ λΌμ΄λΈλ¬λ¦¬λ λ€μν λͺ©μ κ³Ό 볡μ‘μ±μ κ°μ§ μ΄ν리μΌμ΄μ μ ꡬμΆν λ μ¬μ©ν μ μλ λͺκ°μ§ core tool μ μ 곡νλ€. μ΄ν리μΌμ΄μ μ ꡬμΆν λ μΌμμ μΌλ‘ λ°μνλ λ§μ λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν΄ λ€μκ³Ό κ°μ μ€λλ ₯μλ μ¬λ‘λ₯Ό μ 곡νλ€:
Basic Usage
Composable Architecture λ₯Ό μ¬μ©ν΄ κΈ°λ₯μ λΉλνλ €λ©΄, λΉμ μ domain μ λͺ¨λΈλ§ νλ λͺκ°μ§ νμ κ³Ό κ°μ μ μνλΌ: