remember · State Hoisting · Recomposition 관계remember란?재구성(Recomposition) 중에도 값을 유지하기 위한 “상태 저장 장치”
var count by remember { mutableStateOf(0) }
| 개념 | 설명 |
|---|---|
remember | 컴포저블 함수가 재호출되어도 값을 잊지 않게 함 |
mutableStateOf | 값이 변하면 해당 상태를 참조하는 UI만 자동으로 다시 그림 |
| 효과 | 재구성 시에도 상태가 초기화되지 않고 유지됨 |
상태(State)가 변할 때, 해당 상태를 참조하는 UI만 다시 그리는 과정
| 항목 | 설명 |
|---|---|
| 발생 시점 | mutableStateOf()로 관리되는 값이 변경될 때 |
| 동작 방식 | Compose Runtime이 자동으로 UI 부분만 업데이트 |
| 예시 | count++ 시 → Text("Clicked $count") 부분만 새로 그림 |
“상태를 자식이 아니라 부모 쪽에서 기억하고 관리한다”는 의미입니다.
이렇게 하면 부모가 상태를 제어하고, 자식은 단순히 “UI 표현 + 이벤트 전달”만 담당합니다.
@Composable
fun Child() {
var text by remember { mutableStateOf("") } // 자식이 직접 상태 관리
TextField(
value = text,
onValueChange = { text = it }
)
}
📍 문제점:
@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 자동 업데이트자식이 직접 상태를 들고 있지 않고,
부모가 상태를 관리하며 자식에게 값과 이벤트 콜백을 전달하는 패턴
| 비교 | 자식 내부 상태 | 부모 상태 Hoisting |
|---|---|---|
| 선언 위치 | 자식 내부에서 remember 사용 | 부모에서 remember 사용 |
| 제어 권한 | 자식만 제어 가능 | 부모가 제어 가능 |
| 재사용성 | 낮음 | 높음 |
| 데이터 흐름 | 자식 내부 순환 | 부모 → 자식 → 부모 (단방향) |
@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)”과 “이벤트 전달”만 담당한다.
┌──────────────────────┐
│ Parent() │
│ var count by remember│
│ mutableStateOf(0) │
│ │ │
│ ▼ │
│ ChildCounter(count, │
│ onCountChange) │
└──────────┬────────────┘
│
▼
Button → onClick() → 부모의 상태 업데이트
🔁 부모가 상태를 기억하고, 자식은 상태를 표시 및 이벤트만 전달
→ “단방향 데이터 흐름 (Unidirectional Data Flow)” 완성
| 이유 | 설명 |
|---|---|
| ① 상태의 단일 출처 (Single Source of Truth) | 한 상태를 여러 자식이 공유할 수 있음 |
| ② 재구성 시 상태 유지 | 부모가 상태를 기억하므로 자식이 새로 생성되어도 값 유지 |
| ③ 예측 가능한 흐름 (UDF) | 데이터가 항상 부모 → 자식으로 흐름 |
@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")
}
}
📍 이 구조의 장점
| 관점 | 의미 |
|---|---|
| 기능적 관점 | 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)은 여러 개일 수 있고,
부모(노트북)는 항상 최신 내용을 기억하고 있습니다.