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

Jetpack Compose의 derivedStateOf

skydovesJaewoong Eum (skydoves)||7분 소요

Jetpack Compose의 derivedStateOf

Jetpack Compose는 상태 관리 유틸리티로 derivedStateOf를 제공합니다. derivedStateOf는 다른 관찰 가능한 상태(observable state)로부터 값을 계산하여 파생된 상태 객체를 생성합니다. 핵심 이점은 파생 연산이 매 리컴포지션(Recomposition)마다 실행되는 것이 아니라, 입력 상태가 실제로 변경될 때만 재실행된다는 점입니다. 이를 통해 불필요한 연산을 방지하고, 성능에 민감한 시나리오에서 불필요한 리컴포지션을 피할 수 있습니다. 이번 면접 질문을 통하여 아래 내용들을 학습하실 수 있습니다.

  • derivedStateOf가 의존성을 어떻게 추적하고 불필요한 연산을 피하는지 설명할 수 있습니다.
  • derivedStateOfremember(key)를 각각 어떤 상황에서 사용해야 하는지 구분할 수 있습니다.
  • derivedStateOfremember와 함께 올바르게 적용하여 리컴포지션 시 재생성을 방지하는 방법을 이해할 수 있습니다.
  • 오래된 상태(stale state)나 불필요한 연산으로 이어지는 흔한 오용 사례를 식별할 수 있습니다.

derivedStateOf의 내부 동작 원리

derivedStateOfDerivedSnapshotState 객체를 생성합니다. Compose가 처음으로 값을 읽을 때 람다를 평가하고, 실행 도중 읽힌 모든 State 객체를 기록합니다. 이후 다시 값을 읽을 때 Compose는 기록된 의존성 중 변경된 것이 있는지 확인하며, 변경된 의존성이 없으면 람다를 재실행하지 않고 캐시된 결과를 그대로 반환합니다.

이러한 의존성 추적은 스냅샷(snapshot) 시스템을 통해 이루어집니다. 람다 내부에서 읽힌 각 State는 파생 상태의 의존성으로 등록됩니다. 어떤 의존성이 변경되면 파생 상태는 잠재적으로 유효하지 않은 상태(stale)로 표시되며, 다음 읽기 시점에 재평가가 실행됩니다. 다음 예제를 살펴보겠습니다.

@Composable
fun FilteredList(items: List<String>, query: String) {
    val filteredItems by remember(items, query) {
        derivedStateOf {
            items.filter { it.contains(query, ignoreCase = true) }
        }
    }

    LazyColumn {
        items(filteredItems) { item ->
            Text(text = item)
        }
    }
}

위의 예제에서 필터링 람다는 itemsquery가 변경될 때만 실행됩니다. 컴포저블 내의 다른 상태 변경은 필터 연산을 다시 실행하지 않으므로, 불필요한 리스트 재계산을 효과적으로 방지할 수 있습니다.

derivedStateOf와 remember(key) 비교

derivedStateOfremember(key1, key2) { computation() } 중 어떤 것을 사용해야 하는지는 면접에서 자주 등장하는 질문입니다. 핵심적인 차이는 재평가를 무엇이 트리거하느냐에 있습니다.

remember(key)는 키가 변경될 때마다 람다를 재실행하며, 키는 매 리컴포지션에서 동등성(equality) 비교를 수행합니다. 반면 derivedStateOf는 람다 내부에서 읽힌 State 객체가 변경될 때만 재실행됩니다. 이 차이는 State 객체가 자주 변경되지만 파생된 값은 드물게 변경되는 경우에 특히 중요합니다.

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

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

구독하기