
Compose 상태 호이스팅는 단어 뜻만 알고 넘어가면 실제 판단에서 자주 흔들리는 주제입니다. 이 글은 개념보다 먼저 실무에서 어디를 봐야 하는지부터 정리합니다.
핵심은 Compose에서 상태를 UI 내부에 둘지, 부모로 올릴지, ViewModel로 보낼지 판단하는 것입니다. 그래서 단순 요약이 아니라 선택 기준, 실패 신호, 점검 순서를 함께 보겠습니다.

Compose 상태 호이스팅 판단 흐름

Compose 상태 호이스팅을 한 문장으로 이해하면
Compose 상태 호이스팅은 상태를 무조건 ViewModel로 올리는 기술이 아닙니다. 어떤 컴포저블이 상태를 읽고, 어떤 코드가 상태를 바꿔야 하는지 정리하는 설계 습관입니다.
핵심은 상태를 가장 낮은 공통 소유자에게 두고, 아래쪽에는 값과 이벤트만 전달하는 것입니다.
remember가 많아질 때 생기는 신호
처음에는 TextField 하나, Dialog 열림 여부 하나를 remember로 두어도 괜찮습니다. 하지만 화면이 커지면 remember가 여기저기 흩어지고 어느 값이 진짜 화면 상태인지 알기 어려워집니다.
- 같은 상태를 여러 컴포저블에서 따로 remember한다
- 자식 컴포저블이 부모 화면 흐름을 너무 많이 알고 있다
- 테스트하려면 전체 화면을 띄워야만 한다
- 상태 변경 원인을 따라가기 어렵다
stateful과 stateless를 나누는 이유
stateful composable은 상태를 직접 가지고 있는 컴포저블입니다. stateless composable은 상태 값을 받고, 변경 요청을 콜백으로 올립니다. 재사용성과 테스트 관점에서는 stateless 쪽이 보통 더 단순합니다.
@Composable
fun SearchBox(
query: String,
onQueryChange: (String) -> Unit
) {
TextField(
value = query,
onValueChange = onQueryChange,
label = { Text("검색어") }
)
}SearchBox는 query를 직접 remember하지 않습니다. 값을 보여주고, 사용자의 입력 의도만 위로 올립니다.
상태 소유자를 고르는 기준
상태를 어디에 둘지 모를 때는 상태의 수명과 공유 범위를 봅니다. 컴포저블 하나 안에서만 필요한 상태라면 remember가 충분할 수 있습니다. 화면 전체 상태라면 ViewModel이 자연스럽습니다.
- 한 컴포저블 안에서만 쓰면 내부 remember
- 형제 컴포저블이 함께 쓰면 공통 부모로 hoisting
- 화면 회전이나 프로세스 재생성까지 고려하면 rememberSaveable 또는 ViewModel 검토
- 서버 데이터, DB, 비즈니스 규칙과 연결되면 ViewModel 검토
ViewModel과 연결할 때의 기본 형태
ViewModel은 화면 상태를 만들고, Composable은 상태를 읽어서 그립니다. Composable이 직접 repository를 호출하거나 여러 곳에서 상태를 고치기 시작하면 흐름이 쉽게 흐트러집니다.
@Composable
fun SearchRoute(viewModel: SearchViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
SearchScreen(
uiState = uiState,
onQueryChange = viewModel::onQueryChange,
onSubmit = viewModel::submit
)
}이벤트는 상태 변경 권한이 아니다
자식 컴포저블이 상태를 직접 고치기 시작하면 흐름을 추적하기 어렵습니다. onClick, onQueryChange, onRetry 같은 이벤트를 위로 올리고, 실제 상태 변경은 소유자가 결정하게 두는 편이 안전합니다.
이 방식은 코드가 조금 길어 보일 수 있습니다. 하지만 화면이 커질수록 “누가 상태를 바꿨는지”를 추적하기 쉬워집니다.
실무 체크리스트
- 상태를 읽는 컴포저블과 바꾸는 코드를 적어본다
- 가장 낮은 공통 부모를 찾는다
- 자식에는 value와 event callback을 전달한다
- 화면 상태와 일회성 이벤트를 섞지 않는다
- ViewModel은 화면 단위 상태와 비즈니스 흐름에만 사용한다
검색 의도별로 읽는 법
- Compose 입문자는 remember와 rememberSaveable 차이를 먼저 봅니다.
- 실무 화면 구조를 정리하려면 stateful/stateless 분리를 먼저 적용합니다.
- ViewModel 연결이 헷갈리면 Route와 Screen을 나누는 예제를 중심으로 봅니다.
자주 묻는 질문
상태는 무조건 ViewModel로 올려야 하나요?
아닙니다. 한 컴포저블 안에서만 쓰는 임시 UI 상태라면 remember가 더 단순합니다. ViewModel은 화면 단위 상태와 비즈니스 흐름에 적합합니다.
hoisting하면 코드가 너무 길어지지 않나요?
작은 화면에서는 길어 보일 수 있습니다. 하지만 상태 변경 흐름이 명확해져 화면이 커질수록 유지보수에 유리합니다.
rememberSaveable과 ViewModel은 무엇이 다른가요?
rememberSaveable은 저장 가능한 UI 상태를 복원하는 데 가깝고, ViewModel은 화면 상태와 로직의 소유자에 가깝습니다.
마지막 점검 체크리스트
- 같은 상태를 여러 곳에서 remember하고 있지 않은가
- 상태 소유자가 상태를 실제로 바꾸는 책임도 가지고 있는가
- 자식 컴포저블은 value와 callback 중심으로 단순한가
- ViewModel이 UI 세부 배치까지 알고 있지 않은가
- 일회성 이벤트를 지속 상태와 섞지 않았는가
정리
Compose 상태 호이스팅를 제대로 보려면 하나의 정답을 외우기보다 글에서 정리한 기준을 실제 상황에 대입해야 합니다. 핵심은 Compose에서 상태를 UI 내부에 둘지, 부모로 올릴지, ViewModel로 보낼지 판단하는 것입니다.
관련 글로는 Compose remember는 무엇을 기억할까, collectAsStateWithLifecycle은 왜 필요할까, Compose recomposition은 왜 자주 일어날까을 함께 보면 좋습니다. 외부 기준은 Android Compose state, Android Compose state hoisting, Android ViewModel and state holders을 확인했습니다.