Stateful 컴포저블과 Stateless 컴포저블
Stateful 컴포저블과 Stateless 컴포저블
Jetpack Compose에서 컴포저블(composable)은 자체적으로 상태(state)를 관리하는지, 아니면 외부로부터 상태를 전달받는지에 따라 stateful 컴포저블과 stateless 컴포저블로 분류할 수 있습니다. 이 두 가지의 차이를 명확히 이해하는 것은 재사용 가능하고 테스트하기 쉬우며 확장성 있는 UI 컴포넌트를 구축하는 데 핵심적입니다. 면접에서도 Compose의 상태 관리 철학을 얼마나 깊이 이해하고 있는지를 검증하는 대표적인 질문 주제이므로, 반드시 숙지하시길 권장합니다.
이번 면접 질문을 통하여 아래 내용들을 학습하실 수 있습니다.
- Stateful 컴포저블과 stateless 컴포저블의 차이를 정의할 수 있습니다.
- 상태 호이스팅(state hoisting)을 통해 stateful 컴포저블을 stateless로 변환하는 방법을 설명할 수 있습니다.
- 실제 애플리케이션에서 각 유형이 적합한 상황을 판단할 수 있습니다.
- Stateless 패턴이 테스트 가능성과 재사용성을 어떻게 높여 주는지 이해할 수 있습니다.
Stateful 컴포저블
Stateful 컴포저블은 remember와 mutableStateOf 같은 함수를 사용하여 자체적으로 상태를 생성하고 관리합니다. UI 렌더링 로직과 상태 로직이 하나의 컴포저블 함수 안에 함께 캡슐화되어 있어 사용이 간편하지만, 그만큼 결합도가 높아집니다.
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Count: $count")
}
}
위 예제에서 Counter 컴포저블은 count 상태를 직접 소유합니다. 외부 호출자가 이 상태를 읽거나 수정할 수 있는 방법이 없기 때문에, 사용 자체는 간단하지만 유연성이 제한됩니다. 이 상태를 형제(sibling) 컴포저블과 공유하거나, ViewModel에 저장하거나, 다양한 count 값을 주입하여 단위 테스트를 작성하는 것이 불가능합니다.
Stateful 컴포저블은 상태를 외부에서 관찰하거나 변경할 필요가 없는 독립적인 UI 요소에 적합합니다. 대표적인 예로 비밀번호 표시/숨김 토글, 드롭다운 확장 상태, 다른 컴포저블이 알 필요 없는 애니메이션 컨트롤러 등이 있습니다.
주의해야 할 점은 Composition 트리에서 해당 컴포저블이 제거되면 내부 상태도 함께 소실된다는 것입니다. 내비게이션 이벤트나 프로세스 사망(process death)처럼 컴포저블의 수명을 넘어서 상태를 유지해야 하는 경우에는, 반드시 ViewModel이나 rememberSaveable 등의 상태 저장 메커니즘으로 호이스팅해야 합니다. 이 부분은 면접에서도 자주 이어지는 후속 질문이므로 함께 정리해 두시면 좋습니다.
Stateless 컴포저블
Stateless 컴포저블은 어떤 상태도 보유하지 않습니다. 현재 상태를 매개변수로 전달받고, 사용자 인터랙션은 콜백 람다(callback lambda)를 통해 호출자에게 다시 전달합니다. 모든 상태 관리는 외부에서 처리됩니다.
@Composable
fun Counter(count: Int, onIncrement: () -> Unit) {
Button(onClick = onIncrement) {
Text("Count: $count")
}
}
위 코드에서 컴포저블은 전달받은 count 값을 그대로 렌더링하고, 클릭 동작을 onIncrement에 위임합니다. 상태를 어떻게 저장하고, 업데이트하고, 공유할지는 호출자가 결정합니다. 덕분에 정수 값과 클릭 핸들러를 제공하는 어떤 컨텍스트에서든 재사용할 수 있습니다.
Stateless 컴포저블은 순수 함수(pure function)와 동일한 원칙을 따릅니다. 같은 입력이 주어지면 항상 같은 출력을 생성합니다. 따라서 Compose 테스트 API를 사용하여 임의의 값을 전달하고 렌더링 결과를 검증하기만 하면 되므로, 별도의 상태 관리 인프라를 구성할 필요가 없습니다. Android Studio에서 @Preview 어노테이션에 샘플 데이터를 직접 넣어 미리보기를 확인하기에도 편리합니다.