SwiftUI Study – View 간 데이터 전달 시 Binding을 남용하지 않고 ‘단방향 데이터 흐름’을 유지하는 구조 설계하기
Dev Study/SwiftUI 2025. 12. 9. 10:42반응형
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 역할만” 수행해야 한다.
- 이 구조를 따르면 화면이 복잡해져도 일관성과 유지보수성이 크게 향상된다.
반응형

