[Compose Study] State Hoisting (상태 호이스팅)
State Hoisting(상태 호이스팅)이란?
* remember를 사용하여 객체를 저장하는 컴포저블에는 내부 상태가 포함되며 이는 컴포저블을 Stateful로 만든다. 이는 호출자가 상태를 제어할 필요가 없고 상태를 직접 관리하지 않아도 상태를 사용할 수 있는 경우에 유용하다. 그러나 내부 상태를 갖는 컴포저블은 재사용 가능성이 적고 테스트하기가 더 어려운 경향이 있다.
* 상태를 보유하지 않는 컴포저블을 Stateless 컴포저블이라고 한다. 상태 호이스팅을 사용하면 stateless 컴포저블을 쉽게 만들 수 있다.
* Compose에서 상태 호이스팅은 컴포저블을 스테이트리스(Stateless)로 만들기 위해 상태를 컴포저블의 호출자로 옮기는 패턴이다.
Jetpack Compose에서 상태 호이스팅을 위한 일반적 패턴은 상태 변수를 다음 두 개의 매개변수로 바꾸는 것이다.
- value: T - 표시할 현재 값.
- onValueChange: (T) -> Unit - 값이 새 값 T로 변경되도록 요청하는 이벤트.
여기서 이 값은 수정할 수 있는 모든 상태를 나타낸다.
* 상태가 내려가고 이벤트가 올라가는 패턴을 단방향 데이터 흐름(UDF)이라고 하며, 상태 호이스팅은 이 아키텍처를 Compose에서 구현하는 방법이다. (UDF란?)
* 이러한 방식으로 끌어올린 상태에는 중요한 속성이 몇 가지 있다.
- 단일 소스 저장소(Single source of truth): 상태를 복제하는 대신 옮겼기 때문에 소스 저장소가 하나만 있다. 버그 방지에 도움이 된다.
- 공유 가능함(Shareable): 끌어올린 상태를 여러 컴포저블과 공유할 수 있다.
- 가로채기 가능함(Interceptable): 스테이트리스(Stateless) 컴포저블의 호출자는 상태를 변경하기 전에 이벤트를 무시할지 수정할지 결정할 수 있다.
- 분리됨(Decoupled): 구성 가능한 스테이트리스(Stateless) 함수의 상태는 어디든(예: ViewModel) 저장할 수 있다.
스테이트리스(Stateless) 컴포저블은 상태를 소유하지 않는 컴포저블이다. 즉, 새 상태를 보유하거나 정의하거나 수정하지 않는다.
스테이트풀(Stateful) 컴포저블은 시간이 지남에 따라 변할 수 있는 상태를 소유하는 컴포저블이다.
실제 앱에서는 컴포저블의 기능에 따라 컴포저블을 100% 스테이트리스(Stateless)로 하는 것은 어려울 수 있다. 컴포저블이 가능한 한 적게 상태를 소유하고 적절한 경우 컴포저블의 API에 상태를 노출하여 상태를 끌어올릴 수 있도록 컴포저블을 디자인해야 한다.
* 기존 WaterCount를 상태 호이스팅을 통해 리팩토링해보자
@Composable
fun WaterCounter() {
Column(modifier = Modifier.padding(16.dp)) {
var count by rememberSaveable { mutableIntStateOf(0) }
if (count > 0) {
Text(text = "You've had $count glasses.")
}
Button(
onClick = { count = 0 },
modifier = Modifier.padding(start = 8.dp),
enabled = count < 10
) {
Text("Add one")
}
}
}
* StatelessCounter의 역할은 count를 표시하고 count를 늘릴 때 함수를 호출하는 것이다. 이렇게 하려면 위에 설명된 패턴을 따르고 count 상태(구성 가능한 함수에 매개변수로)와 onIncrement 람다(상태가 증가해야 할 때 호출됨)를 전달한다.
@Composable
fun StatelessCounter(count: Int, onIncrement: () -> Unit, modifier: Modifier = Modifier) {
Column(modifier = modifier.padding(16.dp)) {
if (count > 0) {
Text("You've had $count glasses.")
}
Button(onClick = onIncrement, Modifier.padding(top = 8.dp), enabled = count < 10) {
Text("Add one")
}
}
}
* StatefulCounter는 상태를 소유한다. 즉, count 상태를 보유하고 StatelessCounter 함수를 호출할 때 이 상태를 수정한다.
@Composable
fun StatefulCounter(modifier: Modifier = Modifier) {
var count by rememberSaveable { mutableStateOf(0) }
StatelessCounter(count, { count++ }, modifier)
}
* count를 StatelessCounter에서 StatefulCounter로 끌어올린 것이다. 이를 앱에 연결하고 StatefulCounter로 WellnessScreen을 업데이트할 수 있다.
@Composable
fun WellnessScreen(modifier: Modifier = Modifier) {
StatefulCounter(modifier)
}
핵심 사항: 상태를 끌어올릴 때 상태의 이동 위치를 쉽게 파악할 수 있는 세 가지 규칙이 있다.
1. 상태는 적어도 그 상태를 사용하는 모든 컴포저블의 가장 낮은 공통 상위 요소로 끌어올려야 한다(읽기).
2. 상태는 최소한 변경될 수 있는 가장 높은 수준으로 끌어올려야 한다(쓰기).
3. 두 상태가 동일한 이벤트에 대한 응답으로 변경되는 경우 두 상태는 동일한 수준으로 끌어올려야 한다.
이러한 규칙에서 요구하는 것보다 더 높은 수준으로 상태를 끌어올릴 수 있다. 하지만 상태를 충분히 높은 수준으로 끌어올리지 않으면 단방향 데이터 흐름을 따르기가 어렵거나 불가능할 수 있다.
* 상태 호이스팅에는 앞에서 봤듯이 몇 가지 장점이 있다.
1. 스테이트리스(Stateless) 컴포저블을 재사용할 수 있다.
예를 들어, 물과 주스의 잔 개수를 count한다고 해보자
@Composable
fun StatefulCounter() {
var waterCount by remember { mutableStateOf(0) }
var juiceCount by remember { mutableStateOf(0) }
StatelessCounter(waterCount, { waterCount++ })
StatelessCounter(juiceCount, { juiceCount++ })
}
* juiceCount가 수정되면 StatefulCounter가 재구성된다. 리컴포지션 중에 Compose는 juiceCount를 읽는 함수를 식별하고 이러한 함수의 리컴포지션만 트리거한다.
* 사용자가 탭하여 juiceCount를 늘리면 StatefulCounter가 재구성되고 juiceCount를 읽는 StatelessCounter도 재구성된다. 하지만 waterCount를 읽는 StatelessCounter는 재구성되지 않는다.
2. 구성 가능한 스테이트풀(Stateful) 함수는 여러 구성 가능한 함수에 동일한 상태를 제공할 수 있습니다.
@Composable
fun StatefulCounter() {
var count by remember { mutableStateOf(0) }
StatelessCounter(count, { count++ })
AnotherStatelessMethod(count, { count *= 2 })
}
* 이 경우 개수가 StatelessCounter 또는 AnotherStatelessMethod에 의해 업데이트되면 예상대로 모든 항목이 재구성된다.
끌어올린 상태는 공유할 수 있으므로 불필요한 리컴포지션을 방지하고 재사용성을 높이려면 컴포저블에 필요한 상태만 전달해야 한다.
* 핵심 사항: 컴포저블 디자인 권장사항은 필요한 매개변수만 전달하는 것이다.
'Study > Android' 카테고리의 다른 글
[Compose Study] Navigation (0) | 2024.09.09 |
---|---|
[Compose Study] ViewModel에서의 상태 (1) | 2024.09.09 |
[Compose Study] LazyColumn, LazyRow, LazyGrid (1) | 2024.09.06 |
[Compose Study] Compose 기본사항 - 2 (1) | 2024.09.05 |
[Compose Study] Compose 기본사항 - 1 (2) | 2024.09.04 |