
명령형 프로그래밍과 비교하여 Compose 선언형 접근 방식에 있어서 UI요소는 기본적으로 상태가 없는 Stateless라고 일컫는다. 그러나 컴포저블 내부에서 상태 관리가 불가능하다는 것이 아니고, Stateful 컴포저블 또한 존재한다. 여기서 우리는 Stateless와 Stateful에 대한 개념을 한 번 정리하고 갈 필요가 있다.
Compose의 선언형 접근 방식의 핵심은 UI 요소가 상태를 직접적으로 관리하지 않으며, getter와 setter를 노출하지 않는다는 점이다.
Stateless와 Stateful
Stateless는 말 그대로 상태가 없는 것을 뜻하며, 컴포넌트나 UI 요소가 자체적으로 상태를 저장하지 않는다는 뜻이다. 이와 대조되는 개념은 Stateful 상태로 상태를 자체적으로 관리하는 컴포넌트를 의미한다. Jetpack Compose의 컴포저블 함수는 기본적으로는 Stateless를 의미하지만, Stateless와 Stateful 모두 가능하다.
Stateless 상태의 의미
Compose에서 Stateless라고 말할 때, 그것은 UI 요소 자체가 상태를 보유하지 않는다는 것을 의미한다. 즉, Compose의 컴포저블 함수는 위젯을 객체로 노출하지 않으며, getter나 setter를 통해 값을 변경하거나 접근하지 않는다. UI는 외부에서 전달받은 상태를 기반으로 그려진다. 컴포저블 함수는 주어진 입력값에 따라 결과적으로 UI를 렌더링하는 역할만 수행한다.
@Composable
fun GreetingMessage(message: String) {
Text(text = message)
}
위 코드에서 GreetingMessage는 외부에서 전달된 message에 의존하며, 자체적으로 상태를 관리하지는 않는다. 이것이 Statelss 컴포저블의 예시이다.
Stateful 컴포저블
Compose에서 말하는 Stateful 컴포저블은 무엇일까? Stateful 컴포저블은 컴포저블 자체에서 상태를 관리하지만 여전히 외부와 상태 변화에 대한 상호작용이 선언형 방식으로 이루어진다.
Compose에서의 상태 관리는 여전히 getter/setter와 같은 명령형 방식으로 상태를 직접 변경하는 것이 아니다. 상태는 여전히 데이터로 처리되며, 상태 변화에 따라 UI가 재구성된다. 즉, Compose 내의 상태 관리는 전통적인 명령형 프로그래밍에서 객체의 상태를 직접 수정하는 방식과는 다르다. 대신, 상태를 저장하고 변화하는 매커니즘을 컴포저블 내에서 정의할 수 있을 뿐이다.
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Count: $count")
}
}
위의 Counter 컴포저블은 내부적으로 count라는 상태를 관리한다. 그러나 이 상태는 컴포저블 내의 getter/setter로 직접 수정하는 것이 아니다. 상태는 Compose가 제공하는 remember와 mutableStateOf 매커니즘을 통해 관리되며, 상태가 변경되면 UI가 자동으로 재구성된다. 이 방식은 명령형 패러다임의 getter/setter와 달리, 상태가 변경될 때 UI를 자동으로 다시 그리도록 선언적인 방식으로 처리하는 것이다.
Compose의 선언형 접근 방식에서의 핵심 철학
Jetpack Compose의 선언형 접근 방식에서 가장 중요한 개념은 컴포저블 함수는 객체로 노출되지 않으며, getter와 setter 같은 메서드를 제공하지 않는다는 것이다. 이 말은 결국 Comopse에서의 UI 요소들은 내부 상태를 직접 관리하지 않고, 상태를 외부에서 전달받거나, 컴포저블 내에서 선언적으로 관리된다는 의미이다.
Compose에서 getter와 setter를 노출하지 않는다는 의미
UI 요소가 상태를 직접 조작하지 않고, 상태와 UI가 분리된 상태로 동작한다는 것을 의미한다. 기존의 명령형 UI에서 사용하던 방식과 달리, Compose에서는 UI 요소가 내부적으로 상태를 관리하거나 이를 직접적으로 수정하지 않으며, 상태 변화는 컴포저블이 재구성되는 트리거로 작용한다.
- 명령형 방식 : UI 요소가 객체로 노출되고, 상태 직접 수정 가능 ( 예 : button.setText() )
- 선언형 방식 : 상태는 외부에서 전달되거나, 컴포저블 내에서 선언적으로 정의
Stateful 컴포저블의 존재 이유
그렇다면 Stateful 컴포저블이 왜 필요한가에 대한 의문이 생길 수 있다. Stateful 컴포저블은 컴포저블 함수 내에서 필요한 상태를 간단히 관리할 때 사용된다.
예를 들어, 버튼 클릭 횟수나 텍스트 입력과 같은 로컬 상태는 Stateful 컴포저블에서 관리할 수 있다. 하지만 이 역시 상태가 변경되면서 컴포저블이 자동으로 재구성되기 때문에, 명령형 UI에서 사용하는 getter/setter 방식과는 완전히 다른 방식으로 상태가 관리된다.
왜 Compose에서 Stateless 접근이 기본인가?
Jetpack Compose에서 Stateless 상태가 중요한 이유는, UI를 상태와 분리함으로써 더 일관된 사용자 경험을 제공할 수 있기 때문이다. 상태가 외부에서 관리될 때, 상태 변화에 따라 UI가 자동으로 재구성되므로 상태와 UI 간의 불일치가 줄어든다.
그러나 Stateful 컴포저블도 필요에 따라서 존재하며, 이 경우에도 상태는 컴포저블 내에서 선언적으로 관리되며, UI는 상태 변화에 따라 자동으로 갱신된다.
결론
선언형 UI 프레임워크인 Compose에서 Stateless가 아니라 Stateful 컴포저블이 존재한다는 것이 의아할 수 있다.
Stateful 컴포저블은 내부적으로 상태를 관리하지만, 여전히 Stateless처럼 getter와 setter를 노출하지 않으며, 외부와의 상태를 독립적으로 관리하지 않고 선언형 방식으로 상태를 처리한다.
즉, 상태가 있는 컴포저블이지만, 상태 변경 시 UI를 직접 조작하지 않고 상태에 맞춰 UI가 자동으로 재구성된다는 점에서 Stateless처럼 동작하는 면이 있다. 그래서 Stateful 컴포저블도 선언형 패러다임을 따르며 상태와 UI의 일관성을 유지하는 것이다.
결국, Stateful 컴포저블도 실제로는 상태를 관리하는 "방식"만 있을 뿐, 상태와 UI의 결합을 없애는 Stateless 철학을 따르는 것과 같은 맥락이다.
'Android' 카테고리의 다른 글
[Jetpack Compose] Compose의 Side Effect (부작용) (4) | 2024.09.10 |
---|---|
[Jetpack Compose] Composable 수명 주기 (1) | 2024.09.09 |
[Jetpack Compose] Jetpack Compose의 이해 (0) | 2024.09.06 |
[Android] ViewModel의 인스턴스 공유 문제 + hiltNavGraphViewModels (0) | 2024.07.08 |
[Android] ViewPager2에 관련된 속성들 (0) | 2024.05.03 |