위의 TextFieldExample composable 은 내부적으로 state 를 가지고 있는데, stateful 한 함수이다. 내부적으로 state를 가지고 있을 경우 state 에 대한 별도의 처리가 필요하지 않기 때문에 composable caller 가 사용하기 편하기는 하지만,재사용성이 떨어지고 테스트하기 어려워진다.
이때 state hoisting 이라는 것을 이용하여 stateless 한 composable 을 구성할 수 있다.
hoisting 이란 함수안에 선언되는 변수들을 유효범위 최상단에 모아서 선언하는 것을 의미한다.
State hoisting 은 composable caller 에게 state 를 옮겨서, composable 을 stateless 하게 만드는 방법이다.
JetPack Compose 에서 보통 state를
두 부분으로 나누는 패턴을 이용하여 State hoisting 을 구현한다. 이전 포스팅의 TextFieldExample 을 stateless 하게 만든다면 다음과 같이 만들 수 있다.
class StateActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
var name by remember { mutableStateOf("")}
ComposeTestTheme {
TextFieldExample(
state = name,
onValueChange = { name = it }
)
}
}
}
}
@Composable
fun TextFieldExample(state: String, onValueChange: (String)->Unit) {
Column(modifier = Modifier.padding(16.dp)) {
if (state.isNotEmpty()) {
Text(
text = "Hello, $state!",
style = MaterialTheme.typography.h2
)
}
OutlinedTextField(
value = state,
onValueChange = onValueChange,
label = { Text("Name") },
modifier = Modifier.padding(top = 8.dp),
)
}
}
이러한 hoisting의 결과 하나의 Composable 이 State 를 가지기 때문에 관리가 수월해진다. 하지만 하위 composable에 state 를 넘겨야하는 상태라면 함수 파라미터로 계속해서 동일한 State 를 넘겨줘야해서, 최상단의 composable 이 너무 많은 파라미터를 받게되는 현상이 나타날 수 있고, 이로 인해서 오히려 재사용성이 떨어지는 경우도 있을 수 있다. 이럴때는 ViewModel 에서 State 를 관리하도록 하면 해결될 것이다.
이러한 hoist 를 한 state 는 몇가지 중요한 속성을 갖게된다.
Single source of truth : 단일 state 가 주입되기 때문에 state 관리가 수월하다.
Encapsulated : state 를 주입하는 stateful composable 에서만 조작이 가능하다.
Shareable : hoist 된 state 는 여러 composable 에서 사용할 수 있다.
Interceptable: state 에 변화가 있을때 stateful composable 는 이를 적용하지, 무시할지 결정할 수 있다.
Decoupled: 굳이 모든 state 가 composable 내에 위치할 필요가 없어진다.