반응형

SwiftUI Study – View 간 데이터 전달 시 Binding을 남용하지 않고 ‘단방향 데이터 흐름’을 유지하는 구조 설계하기

 

1. 왜 중요한가 (문제 배경)

SwiftUI의 가장 핵심적인 철학은 다음과 같다.

“데이터는 상위 → 하위로 흐르고,
상태 변경은 하위 → 상위로 명확히 전달되어야 한다.”

하지만 실무에서는 다음과 같은 이유로 @Binding이 과다하게 사용되는 경우가 많다.

  • 간편하다는 이유로 모든 값을 Binding으로 전달
  • 실제 상태가 어디에 있는지 헷갈릴 정도로 Binding이 연결됨
  • 하위 View가 상위 View의 상태를 과도하게 제어
  • 여러 View가 같은 Binding 값을 동시에 수정해 레이스 컨디션 발생

Binding 남용은 SwiftUI의 단방향 데이터 흐름을 깨뜨리고,

디버깅이 어려운 구조를 만든다.

 

2. 잘못된 패턴 예시

❌ 예시 1: 단순 “표시만 하는 하위 View”에까지 Binding 전달

struct WrongParent: View {
    @State private var title = "Hello"

    var body: some View {
        WrongChild(title: $title)   // ❌ 불필요한 Binding
    }
}

struct WrongChild: View {
    @Binding var title: String

    var body: some View {
        Text(title)   // ❌ 수정하지 않는데 Binding을 받음
    }
}

문제점

  • Child는 title을 절대 수정하지 않지만 Binding을 받는다
  • 하위 View가 상위 상태를 “불필요하게 알고 있는” 구조
  • 재사용성 감소 — 단순 표시 View인데 상위 상태에 결합됨

❌ 예시 2: 하위 View가 상위 View의 상태를 직접 변경하는 구조 남발

struct WrongEditor: View {
    @Binding var text: String   // 하위가 상위 상태를 강하게 제어

    var body: some View {
        VStack {
            TextField("입력", text: $text)
            Button("초기화") {
                text = ""       // ❌ 상위 상태를 직접 조작
            }
        }
    }
}

문제점

  • 상위 View와 하위 View의 경계가 사라짐
  • 하위 View가 어떤 상태를 언제 수정하는지 추적 어려움
  • 테스트 및 유지보수가 점점 복잡해짐

❌ 예시 3: Binding을 여러 화면으로 연속 전달해 구조가 꼬이는 경우

AView(text: $text)
BView(text: $text)
CView(text: $text)  // ❌ 여러 뷰가 같은 바인딩 값을 동시에 변경

문제점

  • 어디서 text가 변경됐는지 파악 불가
  • 특정 케이스에서 UI가 “깜빡이거나” “원치 않은 타이밍에 업데이트”
  • 상태 중앙 관리가 사라짐

 

3. 올바른 패턴 예시

✅ 예시 1: 읽기 전용 View에는 Binding이 아닌 값(value)만 전달

struct ParentView: View {
    @State private var title = "Hello"

    var body: some View {
        DisplayView(title: title)   // ✔ 값만 전달
    }
}

struct DisplayView: View {
    let title: String

    var body: some View {
        Text(title)
    }
}

장점

  • 하위 View는 “표현 전용 컴포넌트”로 깔끔하게 분리
  • 상위 상태에 대한 결합도가 낮아짐
  • 재사용성 증가

✅ 예시 2: 상태 변경은 Binding 대신 콜백(closure)으로 전달

struct Parent: View {
    @State private var text = ""

    var body: some View {
        EditorView(
            value: text,
            onChange: { text = $0 }     // ✔ 명확한 이벤트 흐름
        )
    }
}

struct EditorView: View {
    let value: String
    let onChange: (String) -> Void

    var body: some View {
        VStack {
            TextField("입력", text: Binding(
                get: { value },
                set: { onChange($0) }
            ))
            Button("초기화") {
                onChange("")            // ✔ 하위가 로직만 전달
            }
        }
    }
}

장점

  • 하위 View는 상태가 아니라 “이벤트만 보고” 동작
  • 상위 View는 상태를 자신이 직접 관리
  • 단방향 데이터 흐름(상위 상태 → 하위 value / 하위 이벤트 → 상위 처리)이 완성됨

✅ 예시 3: 복잡한 상태는 ViewModel에서만 관리하고 View는 이벤트만 전달

@MainActor
final class EditorViewModel: ObservableObject {
    @Published var text: String = ""

    func clear() {
        text = ""
    }
}

struct EditorScreen: View {
    @StateObject private var vm = EditorViewModel()

    var body: some View {
        EditorView(
            value: vm.text,
            onChange: { vm.text = $0 },
            onClear: { vm.clear() }
        )
    }
}

struct EditorView: View {
    let value: String
    let onChange: (String) -> Void
    let onClear: () -> Void

    var body: some View {
        VStack {
            TextField("입력", text: Binding(
                get: { value },
                set: { onChange($0) }
            ))
            Button("초기화") { onClear() }
        }
    }
}

장점

  • ViewModel에서 모든 상태와 로직을 다룸
  • View는 입력·상태·로직에 대해 전혀 알지 않고 “UI 역할”만 담당
  • 구조가 더 커져도 흐름이 일관됨

 

4. 실전 적용 팁

✔ 팁 1 – “수정하지 않는 값”은 절대 Binding으로 넘기지 않는다

읽기 전용 View는 값(value)만 받는 것이 원칙.

✔ 팁 2 – 하위 View는 상태를 갖지 않고 “이벤트”만 상위에 전달한다

수정 권한을 상위에 두면 추적이 쉬워진다.

✔ 팁 3 – 여러 화면에서 동일 값을 다룰 때는 ViewModel 단일 소스로 관리

Binding을 여러 화면에 전파하는 것은 유지보수 악몽.

✔ 팁 4 – Binding은 꼭 필요한 최소한의 경우에만 사용

대표적인 경우: TextField, Toggle 등 SwiftUI 컴포넌트 입력 처리.

✔ 팁 5 – 단방향 데이터 흐름이 유지되면 모든 버그가 추적 가능해진다

“값은 위에서 아래로, 이벤트는 아래에서 위로.”

 

5. 정리

  • Binding은 매우 강력하지만 쉽게 남용될 수 있으며,
    이를 잘못 사용하면 SwiftUI의 단방향 데이터 흐름을 깨뜨린다.
  • 읽기 전용 View에는 값을 전달하고, 상태 변경은 콜백으로 전달하는 것이 정석 패턴이다.
  • 복잡한 입력 화면은 ViewModel에서 상태를 관리하고 View는 “UI 역할만” 수행해야 한다.
  • 이 구조를 따르면 화면이 복잡해져도 일관성과 유지보수성이 크게 향상된다.
반응형
Posted by 까칠코더
,