Replies: 2 comments 11 replies
-
D 구조로 했을때 예제 코드 입니다.
public protocol ReducerableD {
associatedtype State
associatedtype Action
var state: State { get }
func reduce(state: inout State, action: Action)
}
final class DReducer: ReducerableD {
@Published var state: State = .init()
struct State {
var number: Int = 0
}
enum Action {
case increaseNumber
case decreaseNumber
case fetchRandomNumber
}
func reduce(state: inout State, action: Action) {
switch action {
case .increaseNumber:
state.number += 1
case .decreaseNumber:
state.number -= 1
case .fetchRandomNumber:
Task {
do {
self.state.number = try await fetchRandomNumber()
} catch {
print(error.localizedDescription)
}
}
}
}
}
// MARK: - Private Method
private extension DReducer {
func fetchRandomNumber() async throws -> Int {
return try await MockService().fetchRandomNumber()
}
}
import Foundation
public typealias StoreOfD<R: ReducerableD> = Store<R.State, R.Action>
@dynamicMemberLookup
public final class StoreD<State, Action>: ObservableObject {
private var activeTasks: Set<Task<Void, Error>> = []
private let reducer: AnyReducerD<State, Action>
@Published public private(set) var state: State
subscript<T>(dynamicMember keyPath: KeyPath<State, T>) -> T {
self.state[keyPath: keyPath]
}
public init<R: ReducerableD>(reducer: R) where R.State == State, R.Action == Action {
self.reducer = AnyReducerD(reducer)
self.state = reducer.state
}
public func send(_ action: Action) {
reducer.reduce(state: &state, action: action)
}
}
import SwiftUI
struct DView: View {
@EnvironmentObject var store: StoreOfD<DReducer>
var body: some View {
VStack(spacing: .zero) {
Text("\(store.number)")
.padding()
HStack(spacing: .zero) {
Button {
store.send(.increaseNumber)
} label: {
Text("증가")
}
Button {
store.send(.decreaseNumber)
} label: {
Text("감소")
}
Button {
store.send(.fetchRandomNumber)
} label: {
Text("난수")
}
}
}
}
} |
Beta Was this translation helpful? Give feedback.
5 replies
-
개인적인 궁금증!! (편하게 의견말씀해주십셔 🙇♂️)
struct MVIView: View {
@State private var viewModel: ViewModel = .init()
var body: some View {
VStack {
Text("\(viewModel.state.num)")
Text("\(viewModel.num)")
Button {
viewModel.send(action: .asyncTask)
} label: {
Text("감소")
}
}
}
}
#Preview {
MVIView()
}
protocol Reducerable {
associatedtype State
associatedtype Action
var state: State { get }
func send(action: Action)
}
@Observable
@dynamicMemberLookup
final class ViewModel: Reducerable {
var state: State = .init()
subscript<T>(dynamicMember keyPath: KeyPath<State, T>) -> T {
self.state[keyPath: keyPath]
}
struct State {
var num: Int = 0
}
enum Action {
case tapButton
case asyncTask
}
func send(action: Action) {
switch action {
case .tapButton:
state.num += 1
case .asyncTask:
Task {
let num = await delay()
await MainActor.run {
state.num -= num
}
}
}
}
private func delay() async -> Int {
try? await Task.sleep(for: .seconds(2))
return 10
}
} |
Beta Was this translation helpful? Give feedback.
6 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
MVI 아키텍처 구조를 어떤 구조로 가져가는게 좋을까요??
1. 구조A: 가장 간단한 구조 + Effect
2. 구조B: 가장 간단한 구조
User -> Action(Task) -> State -> View -> User
3. 구조C: TCA 커스텀 구조
User -> Action(Effect) -> Reducer -> State -> View -> User
Reducer에서 State, Action를 선언
View는 Store를 참조
Store는 State와 Reducer를 참조
Store: State관리, 사용자에게 Action을 받으면 Reducer에게 전달
Reducer: Action을 받아 State 변경 또는 Effect 방출 하는 순수함수
4. 구조D: TCA 커스텀 구조 - Effect
User -> Action -> Reducer -> State -> View -> User
개인적으로 구현해보면서 D가 가장 깔끔하고 좋은 느낌이였습니다.
Effect로 SideEffect를 책임분리 시키면서 Response Action을 관리하는게 개인적으로 Action이 너무 많아져서 별로였던 경험이 있어 Effect는 두지 않았고
로 가장 깔끔하지 않나 싶습니다.
그리고 이 구조가 Store 구현체가 있어서 @dynamicMemberLookup을 일일이 달아주지 않는것도 좋았습니당
하나 걸리는 점은 비동기 코드를 작성할때 늘 Depth 2가 항상 들어간다는건 단점인데 어떻게 해결하는게 좋을까요~?
Beta Was this translation helpful? Give feedback.
All reactions