Jetpack Compose의 derivedStateOf
Jetpack Compose의 derivedStateOf
Jetpack Compose는 상태 관리 유틸리티로 derivedStateOf를 제공합니다. derivedStateOf는 다른 관찰 가능한 상태(observable state)로부터 값을 계산하여 파생된 상태 객체를 생성합니다. 핵심 이점은 파생 연산이 매 리컴포지션(Recomposition)마다 실행되는 것이 아니라, 입력 상태가 실제로 변경될 때만 재실행된다는 점입니다. 이를 통해 불필요한 연산을 방지하고, 성능에 민감한 시나리오에서 불필요한 리컴포지션을 피할 수 있습니다. 이번 면접 질문을 통하여 아래 내용들을 학습하실 수 있습니다.
derivedStateOf가 의존성을 어떻게 추적하고 불필요한 연산을 피하는지 설명할 수 있습니다.derivedStateOf와remember(key)를 각각 어떤 상황에서 사용해야 하는지 구분할 수 있습니다.derivedStateOf를remember와 함께 올바르게 적용하여 리컴포지션 시 재생성을 방지하는 방법을 이해할 수 있습니다.- 오래된 상태(stale state)나 불필요한 연산으로 이어지는 흔한 오용 사례를 식별할 수 있습니다.
derivedStateOf의 내부 동작 원리
derivedStateOf는 DerivedSnapshotState 객체를 생성합니다. 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)
}
}
}
위의 예제에서 필터링 람다는 items나 query가 변경될 때만 실행됩니다. 컴포저블 내의 다른 상태 변경은 필터 연산을 다시 실행하지 않으므로, 불필요한 리스트 재계산을 효과적으로 방지할 수 있습니다.
derivedStateOf와 remember(key) 비교
derivedStateOf와 remember(key1, key2) { computation() } 중 어떤 것을 사용해야 하는지는 면접에서 자주 등장하는 질문입니다. 핵심적인 차이는 재평가를 무엇이 트리거하느냐에 있습니다.
remember(key)는 키가 변경될 때마다 람다를 재실행하며, 키는 매 리컴포지션에서 동등성(equality) 비교를 수행합니다. 반면 derivedStateOf는 람다 내부에서 읽힌 State 객체가 변경될 때만 재실행됩니다. 이 차이는 State 객체가 자주 변경되지만 파생된 값은 드물게 변경되는 경우에 특히 중요합니다.