면접 질문실전 질문꼬리 질문

Jetpack Compose에서의 상태 호이스팅(State Hoisting)

skydovesJaewoong Eum (skydoves)||8분 소요

Jetpack Compose에서의 상태 호이스팅(State Hoisting)

상태 호이스팅(state hoisting)은 Jetpack Compose에서 하위 컴포저블(composable)이 보유한 상태(state)를 상위 호출자로 끌어올리는 패턴입니다. 이 패턴을 적용하면 하위 컴포저블은 상태를 소유하지 않는 무상태(stateless) 함수가 되며, 현재 값과 이벤트 콜백을 매개변수로 전달받아 렌더링에만 집중하게 됩니다. 이를 통해 단방향 데이터 흐름(unidirectional data flow)이 형성됩니다. 상태는 매개변수를 통해 아래로 흐르고, 이벤트는 콜백을 통해 위로 전파되는 구조입니다.

면접에서 상태 호이스팅은 Compose의 핵심 설계 철학을 이해하고 있는지를 검증하는 대표적인 질문 주제이며, 이 개념을 명확히 이해하고 계시면 상태 관리와 관련된 다양한 후속 질문에도 자신 있게 답변하실 수 있습니다.

이번 면접 질문을 통하여 아래 내용들을 학습하실 수 있습니다.

  • 상태 호이스팅이 컴포저블 계층 구조에서 단방향 데이터 흐름을 어떻게 만들어 내는지 설명할 수 있습니다.
  • 상태를 소비하는 컴포저블의 위치를 기반으로, 상태를 어디까지 끌어올려야 하는지 판단할 수 있습니다.
  • 호이스팅이 컴포저블의 재사용성과 테스트 가능성을 어떻게 높여 주는지 이해할 수 있습니다.
  • 비즈니스 로직과 분리된 무상태 UI 컴포넌트를 구축하는 패턴을 적용할 수 있습니다.
  • 상태 홀더(state holder) 클래스와 ViewModel로의 호이스팅을 언제 선택해야 하는지 구분할 수 있습니다.

단방향 데이터 흐름(Unidirectional Data Flow)

상태가 호이스팅된 모델에서는 상위 컴포저블이 상태를 소유하고, 불변(immutable) 매개변수로 하위에 전달합니다. 하위 컴포저블은 전달받은 매개변수를 기반으로 UI를 렌더링하며, 사용자 인터랙션이 발생하면 람다(lambda)를 통해 상위에 알립니다. 그러면 상위 컴포저블이 상태를 업데이트하고, 리컴포지션(Recomposition)이 트리거되어 새로운 값이 다시 하위로 전달됩니다.

@Composable
fun TemperatureConverter() {
    // 섭씨 온도 상태를 상위에서 관리
    var celsius by remember { mutableStateOf("") }

    TemperatureInput(
        value = celsius,
        onValueChange = { celsius = it }
    )
}

@Composable
fun TemperatureInput(
    value: String,
    onValueChange: (String) -> Unit
) {
    // 상태를 소유하지 않는 무상태 컴포저블
    TextField(value = value, onValueChange = onValueChange)
}

위 예제에서 TemperatureInputremember를 호출하지 않고 가변 상태(mutable state)도 보유하지 않습니다. 매개변수만으로 동작하는 순수 함수(pure function)입니다. 상위 컴포저블인 TemperatureConverter가 상태를 보유하며, 변경 사항에 어떻게 대응할지 결정합니다. 이러한 분리 덕분에 TemperatureInputString과 콜백을 제공하는 어떤 컨텍스트에서도 재사용할 수 있으며, 숨겨진 상태 의존성이 없습니다.

Compose 런타임은 이 패턴으로부터 성능상의 이점을 얻습니다. 하위 컴포저블의 매개변수가 변경되지 않았다면 리컴포지션 자체를 건너뛸 수 있기 때문입니다. 상위 컴포저블이 상태를 보유하고 안정적(stable)이고 불변인 값만 아래로 전달하면, 런타임의 동등성 검사(equality check)가 불필요한 하위 트리의 리컴포지션을 효과적으로 회피할 수 있습니다.

상태를 어디까지 끌어올려야 하는가

상태를 배치할 위치를 결정하는 원칙은 명확합니다. 해당 상태를 읽거나 수정해야 하는 **모든 컴포저블의 최소 공통 조상(lowest common ancestor)**에 호이스팅하면 됩니다. 하나의 컴포저블만 특정 값을 읽고 형제(sibling) 컴포저블은 해당 값이 필요 없다면 상태를 로컬로 유지할 수 있습니다. 반면, 두 개의 형제 컴포저블이 동일한 값에 의존한다면 공유 상위 컴포저블로 상태를 끌어올려야 합니다.

이 면접 질문은 구독자 전용입니다

Dove Letter를 구독하시면 안드로이드, 코틀린 개발 관련 독점 면접 질문의 전체 내용을 볼 수 있습니다.

구독하기