상태 호이스팅(State Hoisting) 🌊
Compose의 장점 중 하나인 Stateless함에 대해 이야기해보려고 합니다.
UI 상태의 상호 의존성을 끊을 수 있다면 UI의 재사용성이 높아지고, 테스트하기도 쉬워지기 때문이죠.
하지만 때때로 UI에 상태를 저장해야 하는 상황도 발생합니다.
예를 들어, TextField와 같은 컴포넌트는 입력된 텍스트를 저장해야 하므로 상태를 가져야 합니다.
이런 경우, Compose의 Stateless한 장점이 사라지고 다시 Stateful하게 됩니다.
이를 해결하기 위한 디자인 패턴이 바로 상태 호이스팅(State Hoisting)입니다. 🚀
상태 호이스팅이란?
상태 호이스팅은 상태를 자식 컴포넌트에서 부모 컴포넌트로 끌어올리는 패턴입니다.
이를 통해 상태를 여러 컴포넌트 간에 공유할 수 있으며, 상태를 관리하는 컴포넌트를 더 유연하고 재사용 가능하게 만들 수 있습니다.
즉, 자식 Composable의 상태를 해당 Composable을 호출하는 부모 쪽으로 끌어올려 자식 Composable을 Stateless하게 만드는 것입니다.
아래 예제에서는 부모 컴포넌트에서 상태를 관리하고, 자식 컴포넌트에 전달하는 방법을 보여줍니다.
@Composable
fun Parent() {
var count by remember { mutableStateOf(0) }
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = "Count: $count")
Child(onIncrement = { count++ })
}
}
@Composable
fun Child(onIncrement: () -> Unit) {
Button(onClick = onIncrement) {
Text("Increment from Child")
}
}
위 코드에서 Parent 컴포넌트는 상태를 관리하고, Child 컴포넌트에 onIncrement 콜백을 전달합니다.
Child 컴포넌트에서 버튼 클릭 시 부모의 상태가 업데이트됩니다.
조금 더 구체적인 예제를 보겠습니다.
다음은 Android Jetpack Compose CodeLab에 있는 예제 코드입니다.
@Composable
fun OnboardingScreen(modifier: Modifier = Modifier) {
var shouldShowOnboarding by remember { mutableStateOf(true) }
Column(
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Welcome to the Basics Codelab!")
Button(
modifier = Modifier.padding(vertical = 24.dp),
onClick = { shouldShowOnboarding = false }
) {
Text("Continue")
}
}
}
여기서 shouldShowOnboarding은 by 키워드를 사용하고 있습니다.
이 키워드는 매번 value를 입력할 필요가 없도록 해주는 속성 위임입니다.
Compose에서는 UI 요소를 숨기지 않고,
단지 컴포지션에 UI 요소를 추가하지 않으므로 Compose가 생성하는 UI 트리에 추가되지 않습니다.
@Composable
fun MyApp(modifier: Modifier = Modifier) {
Surface(modifier) {
if (shouldShowOnboarding) {
OnboardingScreen()
} else {
Greetings()
}
}
}
해당 코드에서는 shouldShowOnboarding에 접근할 수 없습니다.
OnbardingScreen에서 만든 상태를 MyApp Composable과 공유해야 합니다.
상태 호이스팅 적용하기
상태 값을 상위 요소와 공유하는 대신, 상태를 호이스팅하여 공통 상위 요소로 이동하면 됩니다.
@Composable
fun MyApp(modifier: Modifier = Modifier) {
var shouldShowOnboarding by remember { mutableStateOf(true) }
Surface(modifier) {
if (shouldShowOnboarding) {
OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
} else {
Greetings()
}
}
}
이벤트는 어떻게 전달할까요? 아래로 콜백을 전달합니다.
콜백은 다른 함수에 인수로 전달되는 함수로 이벤트가 발생하면 실행됩니다.
MyApp의 상태를 변경할 수 있도록
onContinueClicked: () -> Unit으로 정의된 온보딩 화면에 함수 매개변수를 추가합니다.
@Composable
fun MyApp(modifier: Modifier = Modifier) {
var shouldShowOnboarding by remember { mutableStateOf(true) }
Surface(modifier) {
if (shouldShowOnboarding) {
OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
} else {
Greetings()
}
}
}
@Composable
fun OnboardingScreen(
onContinueClicked: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("Welcome to the Basics Codelab!")
Button(
modifier = Modifier
.padding(vertical = 24.dp),
onClick = onContinueClicked
) {
Text("Continue")
}
}
}
이 방법은 상태가 아닌 함수를 OnboardingScreen에 전달하여 이 컴포저블의 재사용 가능성을 높이고,
다른 컴포넌트가 상태를 변경하지 않도록 보호합니다.
미리보기
온보딩 화면을 호출하기 위해 온보딩 미리보기를 수정하는 방법은 다음과 같습니다.
@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnboardingPreview() {
BasicsCodelabTheme {
OnboardingScreen(onContinueClicked = {}) // Do nothing on click.
}
}
여기서 onContinueClicked를 빈 람다 표현식에 할당하는 것은 아무 작업도 하지 않음을 의미합니다.
결론
상태 호이스팅을 통해 Compose에서의 상태 관리를 더욱 유연하고 효율적으로 할 수 있습니다.
사용된 예제 코드와 대한 자세한 내용은 해당 링크에서 확인해보세요! 😊
'[Android - Compose] > Compose' 카테고리의 다른 글
[Compose] Android Composable Lifecycle (0) | 2024.09.08 |
---|---|
[Compose] Slot-based Layouts (0) | 2024.09.06 |
[Compose] BottomNavigation 사용방법 (0) | 2024.09.06 |
[Compose] LazyListScope (0) | 2024.09.06 |
Android Jetpack Compose 이해하기 (0) | 2024.09.05 |