1 분 소요

Stateful vs Stateless

Compose에서는 State의 상태를 트리거하여 리컴포지션을 통해 화면을 갱신한다. 여기서 remember와 mutableStateOf를 써서 객체를 저장하는 컴포저블은 내부 상태를 생성해서 컴포저블을 Stateful 로 만든다.

Stateless Composable은 상태를 가지지 않는 컴포저블 이다. Stateless가 되기위한 쉬운 방법으로는 상태 호이스팅을 사용하는 것이다. 즉 상태를 상위 컴포넌트로 이동하여 내부적으로 상태를 가지지 않는 것이다. 상태 호이스팅을 이용하면 불필요한 상태가 중복되는것을 막을 수 있고, 버그 발생도 방지가 가능하다.

하지만 Caller가 컴포저블 함수의 상태를 알 필요가 없을 때는 상태 호이스팅 할 필요가 없다.

@Composable
fun ChatBubble(
    message: Message
) {
    var showDetails by rememberSaveable { mutableStateOf(false) } // Define the UI element expanded state

    ClickableText(
        text = message.content,
        onClick = { showDetails = !showDetails } // Apply simple UI logic
    )

    if (showDetails) {
        Text(message.timestamp)
    }
}

위에서 showDetails는 해당 컴포저블 UI 요소의 내부 상태이다. 이 변수는 이 컴포저블에서만 읽고 수정이 되기 때문에 이러한 경우에는 상태를 호이스팅해도 별 다른 이득이 없기 때문에 내부에 유지할 수 있다.

상태 호이스팅

@Preview
@Composable
fun ButtonEx() {
    var count by remember { mutableStateOf(0) }
    Button(
        onClick = { count += 1 },
        contentPadding = PaddingValues(16.dp)
    ) {
        Text(text = "Count: $count")
    }
}

버튼을 클릭하면 count값이 증가하여 Button에 보여주는 컴포저블 함수가 있다. 이 함수는 내부적으로 count를 가지고 있어 Stateful하게 되어있다.

상태를 끌어올리기 위해서는 상태를 두 변수로 나누는 방식으로 끌어올리게 된다.

  • value: T (값)
  • onValueChange: (T) -> Unit (변경하도록 요청하는 함수)
@Composable
fun ButtonEx(
    count: Int,
    onValueChange: (Int) -> Unit
) {
    Button(
        onClick = { onValueChange(count) },
        contentPadding = PaddingValues(16.dp)
    ) {
        Text(text = "Count: $count")
    }
}

@Preview
@Composable
fun ButtonStateless() {
    var count by remember { mutableStateOf(0) }
    ButtonEx(
        count = count,
        onValueChange = { count = it + 1}
    )
    // 마지막 매개변수에 들어간 함수는 람다로 가능
    ButtonEx(
        count = count,
    ) {
        count = it + 1
    }
}

위에서 내부에 있던 상태를 외부로 이동하고 ButtonEx 컴포저블 함수에서는 이벤트와 값만을 받아 Stateless하게 바뀐 것을 볼 수 있다.


References

맨 위로 이동 ↑

댓글남기기