Jetpack Compose - hoisting, 재구성

느린달팽이·2025년 11월 9일

Android

목록 보기
1/4

Jetpack Compose 상태 관리 정리

remember · State Hoisting · Recomposition 관계


1️⃣ remember란?

재구성(Recomposition) 중에도 값을 유지하기 위한 “상태 저장 장치”

var count by remember { mutableStateOf(0) }
개념설명
remember컴포저블 함수가 재호출되어도 값을 잊지 않게 함
mutableStateOf값이 변하면 해당 상태를 참조하는 UI만 자동으로 다시 그림
효과재구성 시에도 상태가 초기화되지 않고 유지됨

2️⃣ Recomposition이란?

상태(State)가 변할 때, 해당 상태를 참조하는 UI만 다시 그리는 과정

항목설명
발생 시점mutableStateOf()로 관리되는 값이 변경될 때
동작 방식Compose Runtime이 자동으로 UI 부분만 업데이트
예시count++ 시 → Text("Clicked $count") 부분만 새로 그림

3️⃣ “부모에 remember를 선언한다”는 뜻은?

“상태를 자식이 아니라 부모 쪽에서 기억하고 관리한다”는 의미입니다.

이렇게 하면 부모가 상태를 제어하고, 자식은 단순히 “UI 표현 + 이벤트 전달”만 담당합니다.


비교 예시 ① ❌ 비권장: 자식 내부에 remember 선언

@Composable
fun Child() {
    var text by remember { mutableStateOf("") } // 자식이 직접 상태 관리
    TextField(
        value = text,
        onValueChange = { text = it }
    )
}

📍 문제점:

  • 자식이 상태를 직접 들고 있어 부모가 제어 불가
  • 다른 자식과 상태 공유 불가
  • 재사용성과 테스트성이 떨어짐

비교 예시 ② ✅ 권장: 부모에 remember 선언 (State Hoisting)

@Composable
fun Parent() {
    var text by remember { mutableStateOf("") } // 부모가 상태를 기억하고 관리
    Child(
        value = text,
        onValueChange = { text = it }
    )
}

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

📍 설명:

  • remember는 부모에 선언 → 부모가 상태를 기억
  • 자식은 단순히 값을 표시하고 이벤트를 부모로 전달
  • 사용자가 입력 시 → onValueChange 콜백 → 부모의 상태(text) 변경 → UI 자동 업데이트

4️⃣ State Hoisting (상태 끌어올리기)

자식이 직접 상태를 들고 있지 않고,

부모가 상태를 관리하며 자식에게 값과 이벤트 콜백을 전달하는 패턴

비교자식 내부 상태부모 상태 Hoisting
선언 위치자식 내부에서 remember 사용부모에서 remember 사용
제어 권한자식만 제어 가능부모가 제어 가능
재사용성낮음높음
데이터 흐름자식 내부 순환부모 → 자식 → 부모 (단방향)

5️⃣ 코드 예시 (Counter 구조)

@Composable
fun ParentCounter() {
    var count by remember { mutableStateOf(0) }
    ChildCounter(
        count = count,
        onCountChange = { count = it }
    )
}

@Composable
fun ChildCounter(count: Int, onCountChange: (Int) -> Unit) {
    Button(onClick = { onCountChange(count + 1) }) {
        Text("Clicked $count times")
    }
}

💡 핵심 요약:

부모는 상태를 “기억(remember)”하고

자식은 “표현(UI)”과 “이벤트 전달”만 담당한다.


6️⃣ 데이터 흐름 구조

┌──────────────────────┐
│      Parent()        │
│  var count by remember│
│  mutableStateOf(0)   │
│          │            │
│          ▼            │
│  ChildCounter(count,  │
│     onCountChange)    │
└──────────┬────────────┘
           │
           ▼
  Button → onClick() → 부모의 상태 업데이트

🔁 부모가 상태를 기억하고, 자식은 상태를 표시 및 이벤트만 전달

→ “단방향 데이터 흐름 (Unidirectional Data Flow)” 완성


7️⃣ 왜 부모에 remember를 선언해야 할까?

이유설명
① 상태의 단일 출처 (Single Source of Truth)한 상태를 여러 자식이 공유할 수 있음
② 재구성 시 상태 유지부모가 상태를 기억하므로 자식이 새로 생성되어도 값 유지
③ 예측 가능한 흐름 (UDF)데이터가 항상 부모 → 자식으로 흐름

8️⃣ 확장 예시 (부모 remember + 자식 분리)

@Composable
fun Parent() {
    var count by remember { mutableStateOf(0) }   // 부모 remember 선언

    Column {
        ChildDisplay(count = count)
        ChildButton(onIncrease = { count++ })
    }
}

@Composable
fun ChildDisplay(count: Int) {
    Text("Count: $count")
}

@Composable
fun ChildButton(onIncrease: () -> Unit) {
    Button(onClick = onIncrease) {
        Text("Increase")
    }
}

📍 이 구조의 장점

  • 상태는 부모가 기억
  • 자식은 Dumb UI (표시 + 콜백만 처리)
  • Recomposition 최소화 → 성능 효율적

9️⃣ 요약: “부모 remember 선언”의 의미

관점의미
기능적 관점remember로 선언된 변수는 재구성 중에도 유지되는 상태 저장소
구조적 관점부모가 이 상태를 기억하면, 자식은 “상태를 사용하는 UI”가 됨
데이터 흐름 관점부모 → 자식(값 전달), 자식 → 부모(이벤트 콜백)
Compose 철학“UI는 상태(State)의 함수이다.” → UI = f(state)

🔟 정리 요약표

키워드설명예시
remember상태를 기억 (재구성 중에도 유지)var text by remember { mutableStateOf("") }
mutableStateOf값이 변하면 자동으로 UI 업데이트count++ 시 UI 자동 갱신
State Hoisting상태를 부모로 끌어올려 제어 일원화Parent()가 상태 기억, Child()는 UI 표현
UDF (단방향 데이터 흐름)상태 → UI → 이벤트 → 상태 순환부모→자식→부모 구조

비유로 이해하기

부모가 상태를 “기억”하는 건,

“노트북에서 문서 파일을 열고, 여러 탭(자식들)에서 같은 문서를 공유하는 것”과 같아요.

파일(상태)은 하나지만, 탭(UI)은 여러 개일 수 있고,

부모(노트북)는 항상 최신 내용을 기억하고 있습니다.

profile
한걸음이라도 제대로... 쓰임있는 앱을 만들자

0개의 댓글