안드로이드 시스템·Compose 용어 사전
Compose리컴포지션 · 선언형 UI · mutableStateOf

recomposition

상태가 바뀌면 그 상태를 읽은 컴포저블만 다시 실행하는 것. Compose의 동작 원리.

상태가 바뀌면 그 상태를 읽은 컴포저블만 다시 실행하는 것.

선언형 — UI는 상태의 함수

  • 명령형 (View) — findViewById → setText → setVisibility → notifyDataSetChanged
    • "어떻게 바꿀지" 를 단계마다 지시한다
    • 상태와 화면이 어긋날 여지가 매 단계 생긴다

선언형 (Compose) UI = f(상태)

  • "이 상태면 이렇게 보인다" 만 적는다
  • 갱신은 런타임이 한다
@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    Column {
        Text("클릭 $count 회")                       // count 를 읽는다 → 여기가 다시 실행된다
        Button(onClick = { count++ }) { Text("증가") }  // 읽지 않는다 → 건너뛴다
    }
}

읽은 곳만 다시 실행된다

mutableStateOf 는 스냅샷 시스템에 등록된 관찰 가능한 상태다 Composable 이 그 값을 읽으면 '이 스코프가 이 상태를 구독한다' 가 기록된다

  • count++ → 구독한 스코프(Text 를 감싼 람다) 만 무효화 → 그 범위만 재실행
  • Column 전체도, Button 도 다시 그려지지 않는다

컴포저블은 순수해야 한다

  • 같은 입력이면 같은 출력 (부수효과 없음)
  • 순서에 의존하지 않는다 (병렬로 실행될 수 있다)
  • 여러 번 실행돼도 안전해야 한다 (한 프레임에 여러 번 불릴 수 있다)
  • 건너뛸 수 있어야 한다
// ❌ 컴포지션 중에 부수효과를 낸다 — 리컴포지션마다 반복된다
@Composable
fun Screen(vm: MyViewModel) {
    vm.loadData()                       // 스크롤할 때마다 API 를 친다
    analytics.log("screen_view")        // 로그가 수십 번 찍힌다
}

// ✅ 효과는 전용 API 안에서
LaunchedEffect(Unit) { vm.loadData() }

얼마나 자주 도나

애니메이션 중에는 초당 60~120회 돈다

  • 컴포저블 안에서 정렬 · 필터링 · 객체 생성을 하면 그만큼 반복된다
  • 무거운 계산은 remember 로 감싸거나 ViewModel 로 올린다

면접 함정

  • "상태가 바뀌면 화면 전체를 다시 그린다" → 읽은 스코프만이다. 이게 성능의 핵심이다.
  • "var count = 0으로도 된다"remember가 없으면 리컴포지션마다 초기화되고, mutableStateOf가 없으면 변경이 관측되지 않는다.

세 단계

  • 컴포지션(Composition) — 무엇을 그릴지 결정한다

  • 레이아웃(Layout) — 크기를 재고 위치를 정한다

  • 드로잉(Drawing) — 실제로 그린다

  • 상태를 어느 단계에서 읽느냐가 비용을 정한다

    • 컴포지션에서 읽으면 → 세 단계 전부 다시
    • 레이아웃에서 읽으면 → 레이아웃 + 드로잉
    • 드로잉에서 읽으면 → 드로잉만

위치 기억(positional memoization)

@Composable
fun List(items: List<String>) {
    items.forEach { item ->
        Text(item)                  // 호출 '위치' 로 슬롯을 식별한다
    }
}
Compose 는 컴포저블 호출의 '소스 위치' 로 컴포지션 슬롯을 식별한다
  → 같은 위치의 호출은 같은 remember 슬롯을 쓴다
  → 목록에서 항목 순서가 바뀌면 상태가 엉뚱한 항목으로 간다
  → key(item.id) { } 또는 items(key = ...) 로 정체를 명시한다

리컴포지션 횟수를 직접 본다

@Composable
fun RecompositionCounter(tag: String) {
    val count = remember { mutableIntStateOf(0) }
    SideEffect { count.intValue++ }              // 리컴포지션이 성공할 때마다
    Text("$tag: ${count.intValue}")
}

Android Studio 의 'Layout Inspector → Recomposition counts' 로도 볼 수 있다 스크롤 한 번에 수천 번이 찍히면 그 컴포저블이 범인이다

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — Jetpack Compose 기초 — 선언형·recomposition