본문 바로가기
Android

[Android] jetpack compose : State Hoisting(상태 호이스팅)

by 박매트 2025. 3. 7.

State Hoisting(상태 호이스팅)이란?

컴포저블 함수 내부에서 상태를 직접 관리하는 대신, 상위 컴포저블로 상태를 끌어올려(State Lift) 관리하는 패턴을 의미합니다.

📌 왜 State Hoisting을 사용할까?

  1. 재사용성 증가: 상태를 특정 컴포넌트에 묶지 않고, 상위에서 관리하면 여러 하위 컴포넌트에서 공유 가능
  2. 단방향 데이터 흐름 유지: 데이터 흐름이 예측 가능하고 유지보수 쉬워짐
  3. 컴포저블의 책임 분리: UI 컴포저블은 UI만 담당하고, 상태 관리는 상위에서 담당

🚀 예제: State Hoisting 적용 전 vs 후

상태를 내부에서 관리하는 잘못된 예시

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) } // 내부에서 직접 상태 관리

    Column {
        Text("Count: $count")
        Button(onClick = { count++ }) {
            Text("Increase")
        }
    }
}
  • Counter 내부에서 상태를 직접 관리하기 때문에 외부에서 제어할 수 없음
  • 다른 UI에서 count 값을 변경하고 싶다면 어려움 발생

State Hoisting 적용한 올바른 예시

@Composable
fun Counter(count: Int, onIncrement: () -> Unit) {
    Column {
        Text("Count: $count")
        Button(onClick = onIncrement) { // 상태 변경을 상위에서 처리
            Text("Increase")
        }
    }
}

@Composable
fun CounterApp() {
    var count by remember { mutableStateOf(0) } // 상위에서 상태 관리

    Counter(count = count, onIncrement = { count++ }) // 하위에 상태 전달
}
 
  • Counter 컴포저블이 직접 count를 관리하지 않고, 외부에서 상태를 받음
  • CounterApp에서 count 상태를 관리하고, 변경 로직(onIncrement)도 여기서 처리
  • 다른 컴포넌트에서도 count를 공유 가능

🎯 State Hoisting 패턴

State Hoisting은 보통 두 개의 매개변수로 표현됩니다.

  1. 상태 변수 (value)
  2. 상태를 변경하는 콜백 함수 (onValueChange)

State Hoisting 일반적인 구조

@Composable
fun SomeComponent(value: String, onValueChange: (String) -> Unit) {
    TextField(value = value, onValueChange = onValueChange)
}

@Composable
fun ParentComponent() {
    var text by remember { mutableStateOf("") }

    SomeComponent(value = text, onValueChange = { text = it }) // 상태를 부모가 관리
}
  • SomeComponent는 직접 value를 관리하지 않음
  • ParentComponent에서 상태를 소유하고 onValueChange를 통해 하위 컴포넌트에 전달

🎯 State Hoisting이 필요한 상황

여러 컴포넌트에서 같은 상태를 공유해야 할 때
상태 변경 로직을 한 곳에서 관리하고 싶을 때
UI 컴포넌트가 상태 관리 없이 재사용 가능해야 할 때


🚀  정리

State Hoisting컴포저블 내부에서 상태를 직접 관리하는 것이 아니라, 상위 컴포저블로 끌어올려 관리하는 패턴이다.
이를 통해 재사용성, 유지보수성, 예측 가능성이 향상되며, 단방향 데이터 흐름(Unidirectional Data Flow, UDF)을 유지할 수 있다! 🚀

 

하위 컴포저블은 UI만 담당하고, 상태 관리는 상위에서! 🔥

 

 

State Hoisting과 Recomposition(재구성)의 관계

State Hoisting(상태 호이스팅)과 Recomposition(재구성)은 Jetpack Compose의 상태 관리와 성능 최적화에서 중요한 개념입니다.
간단히 말해, State Hoisting을 올바르게 사용하면 불필요한 Recomposition을 줄이고 앱 성능을 향상시킬 수 있습니다.


🎯 Recomposition(재구성)이란?

  • Compose에서는 상태가 변경되면 UI를 다시 그리는 과정Recomposition(재구성)이라고 함.
  • 예를 들어, mutableStateOf() 값을 변경하면, 이를 참조하는 @Composable 함수가 다시 실행됨.
  • 하지만 불필요한 Recomposition이 발생하면 성능 저하가 발생할 수 있음.

🚀 State Hoisting이 Recomposition을 줄이는 이유

State Hoisting은 상태를 상위 컴포넌트에서 관리하는 방식이므로, 불필요한 UI 업데이트를 방지하는 효과가 있음.

State Hoisting을 사용하지 않은 경우 (비효율적인 Recomposition 발생)

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) } // 내부에서 직접 상태 관리

    Column {
        Text("Count: $count")
        Button(onClick = { count++ }) { 
            Text("Increase") 
        }
    }
}
  • count가 변경될 때마다 Counter 함수 전체가 Recomposition됨
  • 하지만 Text("Increase")는 상태와 관계없는 UI 요소인데도 불필요하게 다시 그려짐
  • 해결책?State Hoisting을 활용하여 최소한의 Recomposition 유지

State Hoisting을 적용한 경우 (Recomposition 최적화)

@Composable
fun Counter(count: Int, onIncrement: () -> Unit) {
    Column {
        Text("Count: $count") // count 변경 시 여기는 재구성됨
        Button(onClick = onIncrement) { 
            Text("Increase") // ❌ 이 부분은 Recomposition 되지 않음!
        }
    }
}

@Composable
fun CounterApp() {
    var count by remember { mutableStateOf(0) } // 상위에서 상태 관리

    Counter(count = count, onIncrement = { count++ }) // 하위에 상태 전달
}
  • Counter의 내부 UI(버튼, 레이아웃)는 그대로 유지되고, Text("Count: $count") 부분만 재구성됨!
  • count 값이 변경될 때, Button은 Recomposition 되지 않아서 성능 최적화 가능!

🎯 State Hoisting을 통한 Recomposition 최적화 원칙

  1. 상태를 필요하지 않은 곳에서 관리하지 않는다.
    • UI가 필요로 하는 최소한의 범위에서만 Recomposition을 발생시키기 위해 상위에서 관리
  2. Compose의 데이터 흐름을 유지 (Unidirectional Data Flow, UDF)
    • 하위 컴포저블은 상태를 직접 관리하지 않고, 매개변수로 전달받아 UI만 담당
    • 상태를 변경하는 로직은 상위 컴포저블에서 처리
  3. 컴포넌트를 작게 나누어 Recomposition 최소화
    • Compose는 변경된 상태만 감지하여 업데이트하지만, 컴포넌트가 크면 불필요한 부분까지 다시 그림
    • 따라서 UI 요소를 재사용 가능한 작은 컴포넌트로 나누면 불필요한 Recomposition을 줄일 수 있음!

🚀 결론

State Hoisting을 올바르게 사용하면, 불필요한 Recomposition을 줄이고 성능을 최적화할 수 있음!
하위 컴포넌트는 UI만 담당하고, 상태 관리는 상위에서!
Compose의 Recomposition 원리를 이해하고 최소한의 UI 업데이트를 유지하는 것이 중요! 🚀