@State를 함부로 붙이지 말자 (그 View에서 바꾸는 값만!)

SteadySlower·2022년 8월 19일
0

SwiftUI

목록 보기
27/64
post-custom-banner

SwiftUI를 쓰다보면 가끔 하위 View에서도 @State를 붙여야 하는 것이 아닌가라는 생각이 들기도 합니다. 하지만 @State는 아주 제한적으로만 써야 합니다.

@State는 그 변수가 바뀌는 View 안에만 선언되어야 한다.

아래 코드를 보겠습니다. 아래 코드는 MainView - FirstView - SecondView의 3단 구조로 되어 있는 View입니다.

원칙은 해당 변수가 바뀌는 View에서만 선언해야 합니다. 아래 코드에서는 text는 MainView에서 TextField를 통해서만 수정이 됩니다. 이렇다면 무조건 MainView에서만 @State로 선언을 해야합니다.

import SwiftUI

struct MainView: View {
    @State var text = ""
    
    var body: some View {
        VStack(alignment: .leading) {
            TextField("main text field", text: $text)
            FirstView(firstText: text)
        }
    }
}

struct FirstView: View {
    let firstText: String
    
    var body: some View {
        VStack {
            Text("first text: \(firstText)")
            SecondView(secondText: firstText)
        }
    }
}

struct SecondView: View {
    let secondText: String
    
    var body: some View {
        Text("second text: \(secondText)")
    }
}

이렇게 해야지만 원하는 의도대로 작동하게 됩니다.

SwiftUI의 입장에서 생각을 해봅시다. SwiftUI가 View를 다시 그리는 기준은 @State로 선언된 변수가 바뀔 때 입니다. 따라서 text가 바뀌게 되면 text를 받아서 그리는 View인 FirstView가 re-render됩니다. 당연히 FirstView의 하위 View인 SecondView도 re-render됩니다.

결국 @State 변수가 바뀌는 것이 거기에 연결된 모든 하위 View를 re-render 시켜주는 것이죠. 따라서 @State 변수는 해당 값을 변경 시키는 View에서만 선언해야 합니다.

🧪 하위 View에서 @State로 데이터를 받는 경우

그렇다면 한번 실험을 통해서 이 이론을 뒷받침 해보겠습니다. 이번에는 SecondView 내부의 secondText @State로 선언해보겠습니다. 물론 그 값은 상위 View에서 받습니다.

import SwiftUI

struct MainView: View {
    @State var text = ""
    
    var body: some View {
        VStack(alignment: .leading) {
            TextField("main text field", text: $text)
            FirstView(firstText: text)
        }
    }
}

struct FirstView: View {
    let firstText: String
    
    var body: some View {
        VStack {
            Text("first text: \(firstText)")
            SecondView(secondText: firstText)
        }
    }
}

struct SecondView: View {
    @State var secondText: String //👉 여기서 State로 선언
    
    var body: some View {
        Text("second text: \(secondText)")
    }
}

이를 실험해보면 첫 번째 코드와는 달리 SecondView의 text가 바뀌지 않는 것을 볼 수 있습니다.

SwiftUI의 입장에서 생각을 해봅시다. SwiftUI는 @State로 선언된 변수가 변경될 때만 View를 re-render합니다. 따라서 MainView의 text가 바뀌면 연결된 FirstView를 re-render하고 first text가 변하는 것입니다.

하지만 SecondView의 Text()는 secondText라는 @State 변수에 연결이 되어 있습니다. 따라서 해당 변수가 명시적으로 변경되기 전까지 re-render가 되지 않는 것입니다.

즉 결론적으로 하위 View @State는 비록 상위 View에서 데이터를 받더라도 독립적입니다.

🧪 하위 View에 @State로 선언된 변수는 상위 View와 독립적이다.

위에서 세운 이론을 또 하나의 실험으로 증명을 해봅시다. 이번에는 SecondView에 TextField를 하나 더 넣어보겠습니다. 그리고 SecondView에 선언된 @State 변수인 secondText와 연결합니다.

이번에는 독립성을 좀 더 뚜렷하게 보기 위해서 text에 빈문자열이 아닌 “Fruit”라는 기본값을 주겠습니다.

import SwiftUI

struct MainView: View {
    @State var text = "Fruit"
    
    var body: some View {
        VStack(alignment: .leading) {
            TextField("main text field", text: $text)
            FirstView(firstText: text)
        }
    }
}

struct FirstView: View {
    let firstText: String
    
    var body: some View {
        VStack {
            Text("first text: \(firstText)")
            SecondView(secondText: firstText)
        }
    }
}

struct SecondView: View {
    @State var secondText: String
    
    var body: some View {
        VStack {
            Text("second text: \(secondText)")
            TextField("second text field", text: $secondText)
        }
    }
}

이 코드를 실행해보면 아래와 같은 결과가 나타납니다. 초기에는 text의 값을 secondText를 init할 때 사용했기 때문에 MainView의 text와 seconText가 동일한 값을 가지고 있습니다. 하지만 MainView에 있는 TextField로는 FirstView의 Text만 변경할 수 있고 SecondView의 TextField로는 SecondView에 있는 Text만 변경할 수 있습니다.

결과적으로 init될 때 값을 공유할 수는 있지만 init이 끝나는 순간 하위 View의 @State는 SwiftUI와 독립적인 관계를 가지고 있습니다. 따라서 상위 View가 re-render되더라도 SecondView 안에 있는 Text는 업데이트 되지 않고 SecondView 안에서 변경해야만 업데이트 되는 것이죠.

마치며…

이 포스팅을 시작하게 된 계기는 개인 프로젝트의 리팩토링을 하면서 입니다.

이 작업은 제가 기능 개발 위주로 프로젝트를 진행하다가 View의 body 부분이 너무 커지면서 View를 좀 쪼개서 가독성이 높은 코드를 만들기 위해서 실시한 것입니다.

하지만 이 작업을 하면서 하나의 View를 여러 개의 수직적인 View 계층으로 나누면서 어디에 @State를 써야하는지 고민을 많이 했습니다. 그래서 여러번의 시행착오를 통해 이번 포스팅의 결과를 얻게 되었습니다.

SwiftUI로 프로젝트를 하면 할 수록 점점 더 SwiftUI에 대해서 알아가는 것 같아 좋습니다.

profile
백과사전 보다 항해일지(혹은 표류일지)를 지향합니다.
post-custom-banner

0개의 댓글