웹의 리액트(React)와 앱의 제트팩 컴포즈(Jetpack Compose)
명령형으로 작성되던 바닐라js를 선언적으로 작성할 수 있도록 도와주는 리액트와 같이,
Jetpack Compose는 명령형 안드로이드 코틀린 코드를 선언형으로 만들 수 있게 도와주는 UI 툴킷이다.
이 둘의 차이를 상태 관리와 생명주기를 중심으로 비교해보고자 한다.
그 전에, 각각의 기본 단위를 먼저 알아보자.
리액트의 컴포넌트
리액트에서는 화면을 구성하는 기본 단위가 컴포넌트이다.
컴포넌트는 props를 받아 화면을 그리고, 상태를 가질 수 있다.
상태나 props가 변경되면 컴포넌트가 다시 호출되어 UI가 갱신된다.
제트팩 컴포즈의 컴포저블 함수
제트팩 컴포즈에서는 컴포저블 함수가 화면 구성의 기본 단위이다.
컴포저블 함수는 @Composable 어노테이션을 붙여 UI를 선언하며,
이 어노테이션을 붙이면 상태나 생명주기를 추적하고, 재구성할 수 있게 됩니다.
리액트의 상태 관리
리액트는 useState를 통해 컴포넌트 상태를 선언적으로 관리한다.
상태가 변경되면 컴포넌트 전체가 다시 렌더링되고, Virtual DOM을 통해 실제 DOM과 비교(diffing)하여 변경사항만 적용한다.
상태는 컴포넌트 단위로 보존되며, 컴포넌트가 리렌더링 되더라도 useState로 선언한 값은 유지된다.
제트팩 컴포즈의 상태 관리
컴포즈는 remember와 mutableStateOf를 이용해 상태를 관리한다.
var count by remember { mutableStateOf(0) }
remember는 이 상태를 컴포저블 함수 호출이 반복되어도 기억하라는 의미
mutableStateOf는 값이 변하면 알아서 다시 그리라는 의미
상태가 변경되면 해당 컴포저블 함수만 다시 호출된다.
Virtual DOM 개념이 없고, 빌드 타임 최적화를 통해 성능을 확보한다.
컴포즈의 빌드 타임 최적화란?
코틀린 컴파일러 플러그인을 통해 앱을 빌드할 때 컴포저블 함수의 상태 의존성과 호출 구조를 분석하고, 이를 기반으로 최적화된 코드를 미리 생성하는 것이다.
상태는 함수가 다시 호출되더라도 remember 덕분에 보존된다.
리액트 예시
import { useState } from "react"; function Counter() { const [count, setCount] = useState(0); return <button onClick={() => setCount(count + 1)}>클릭 수: {count}</button>; }
제트팩 컴포즈 예시
@Composable fun Counter() { var count by remember { mutableStateOf(0) } Button(onClick = { count++ }) { Text(text = "클릭 수: $count") } }
리액트의 생명주기
컴포넌트 중심으로 라이프사이클이 관리된다.
Mount: 컴포넌트가 처음 화면에 그려질 때Update: 컴포넌트의 props나 state가 변경되어 다시 렌더링될 때Unmount: 컴포넌트가 화면에서 사라질 때
생명주기 관리
useEffect 훅을 통해 각 단계에서 side effect를 실행하며, 의존성 배열을 통해 실행 시점을 조정한다.
제트팩 컴포즈의 생명주기
컴포저블 함수 스코프를 중심으로 라이프 사이클이 관리된다.
컴포저블 함수 호출 자체를 하나의 라이프 사이클 단위로 삼아 관리한다. 각 함수 호출이 Composition, Recomposition, Disposal 흐름을 따르게 된다.
생명주기 관리
컴포즈는 각 단계에 따라 다음과 같은 선언형 API를 사용한다.
최초 Composition
LaunchedEffect를 사용해 초기화 작업을 실행한다.
Recomposition
SideEffect를 사용해 UI가 갱신된 후 추가 작업을 실행한다.
Disposal
DisposableEffect를 사용해 컴포저블이 사라질 때 리소스를 정리한다.
리액트의 의존성 배열처럼, 컴포즈는 의존성 키를 사용해 재실행 시점을 조정한다.
LaunchedEffect(count) { // count 변경 시마다 실행 }
결과적으로 리액트와 제트팩 컴포즈는 매우 비슷한 흐름을 가지고 있지만, 내부적인 작동 방식과 디테일에 차이가 있다는 느낌을 받았다.
특히, 컴포즈에서 ViewModel을 함께 사용하게 되면, 상태 관리 방식에서도 추가적인 차이가 생긴다고 한다. 이 부분도 앞으로 공부해보고, 컴포즈를 이해해나가보려고 한다.