Compose 공식 문서를 참고해 번역한 내용입니다.
Compose 이해
Compose는 선언형 UI 도구
- 선언형 API를 제공하여 앱 UI를 쉽게 관리
선언형 프로그래밍 패러다임 (Declarative Programming Paradigm)
기존 Android View 계층 구조는 UI 위젯의 트리로 표시
- findViewById 같은 함수를 사용해 트리를 탐색하고 메서드를 호출해 노드를 변경하는 방식
기존 방식의 문제점
- 휴먼에러가 발생할 가능성이 크다
대안으로 선언형 UI 프로그래밍으로 전환
- 화면 전체를 개념적으로 재생성하고 필요한 변경사항만 적용하는 방식
- Compose는 선언형 UI Framework
Compose는
Data를 받아 Composable 함수들을 정의하여 UI 요소를 내보내 UI를 빌드할 수 있다
간단한 Composable 함수
(name이라는 String 데이터를 받아 Text UI를 내보낸다)
@Composable
fun Greeting(
name: String,
modifier: Modifier = Modifier
) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
Composable의 특징
1. @Composable 주석으로 지정되어야 한다.
데이터를 UI로 변환하기 위한 함수라는 것을 컴파일러에게 알리는 역할
@MustBeDocumented
@Retention(AnnotationRetention.BINARY)
@Target(
// function declarations
// @Composable fun Foo() { ... }
// lambda expressions
// val foo = @Composable { ... }
AnnotationTarget.FUNCTION,
// type declarations
// var foo: @Composable () -> Unit = { ... }
// parameter types
// foo: @Composable () -> Unit
AnnotationTarget.TYPE,
// composable types inside of type signatures
// foo: (@Composable () -> Unit) -> Unit
AnnotationTarget.TYPE_PARAMETER,
// composable property getters and setters
// val foo: Int @Composable get() { ... }
// var bar: Int
// @Composable get() { ... }
AnnotationTarget.PROPERTY_GETTER
)
annotation class Composable
2. 함수는 매개변수로 데이터를 받아 UI에 표시
3. 함수는 다른 Composable 함수를 호출할 수 있다
4. 함수는 아무것도 반환하지 않는다
5. 함수는 빠르고 멱등성의 성질을 가지며 side-effects(부작용)이 없다 (동일한 input은 동일한 output)
6. 동적일 수 있다 (Dynamic Contents)
@Composable
fun Greeting(
name: String,
modifier: Modifier = Modifier
) {
if (name.isEmpty()) {
Text(
text = "Hello!",
modifier = modifier
)
} else {
Text(
text = "Hello! $name",
modifier = modifier
)
}
}
Recomposition (재구성)
기존 View 시스템에서는 setter를 호출하여 내부 상태를 변경하지만
Compose는 새로운 데이터를 사용해 Composable을 다시 호출한다 (재구성)
smart recomposition을 통해 변경된 컴포넌트만 재구성할 수 있다
예시) 버튼을 클릭하면 MainScreen의 clicks 데이터가 변해 새로운 clicks 데이터로 ClickCounter를 다시 호출
@Composable
fun MainScreen() {
val clicks = remember { mutableIntStateOf(0) }
ClickCounter(
clicks = clicks.intValue,
onClick = { clicks.intValue++ }
)
}
@Composable
fun ClickCounter(
clicks: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
Button(modifier = modifier, onClick = onClick) {
Text("Clicked $clicks times")
}
}
Composable은 잘못 사용하게 되면 재구성이 많은 빈도로 일어나게 되어 성능에 영향을 줄 수 있다
Compose를 사용할 때 아래와 같은 사항을 항상 생각해야 한다
1. Recomposition은 최대한 많은 수의 Composable 함수와 람다를 건너뛴다
Recomposition을 통해 일부 요소를 skip할 수 있다
@Composable
fun Greeting(
firstName: String,
lastName: String,
modifier: Modifier = Modifier
) {
Column(modifier = modifier) {
// firstName 변경 시 recomposition, lastName만 변경 시 skip
Text(
text = "Hello! $firstName",
modifier = modifier
)
Column {
// lastName 변경 시 recomposition, firstName만 변경 시 skip
Text(
text = "Hello! $lastName",
modifier = modifier
)
}
}
}
2. Recomposition은 낙관적의며 취소될 수 있다
Compose는 매개변수가 다시 변경되기 전에 Recomposition을 완료할 것으로 예상
완료되기 전에 매개변수가 변경되면 Recomposition을 취소(UI 트리 삭제)하고 새 데이터를 사용해 다시 Recomposition 시작
3. Composable 함수는 애니메이션의 모든 프레임에서와 같은 빈도로 매우 자주 실행될 수 있다
자주 실행되면 앱 성능에 영향을 줄 수 있다.
만약 비용이 많이 드는 작업을 해야 한다면 Composable 외부로 이동하고 mutableStateOf 같은 함수를 사용해 데이터를 전달해야 한다
4. Composable 함수는 동시에 실행할 수 있다
현재는 동시에 실행할 수 없지만 Compose는 멀티스레드 지향적으로 설계되어 이런 방식으로 작성하는 것이 좋다
5. Composable 함수는 순서와 상관없이 실행할 수 있다
Composable 함수가 호출된 순서대로 실행된다고 가정할 수 있지만 반드시 그런 것은 아니다
Compose에는 우선순위가 높은 일부 UI 요소를 먼저 그릴 수 있는 옵션이 있다
'Compose' 카테고리의 다른 글
[Compose] Compose Phases (0) | 2025.02.09 |
---|---|
[Compose] Composable 수명 주기 (0) | 2025.02.09 |