Ch4. Expo UI — SwiftUI·Compose 선언형 네이티브
SDK 56에서 stable이 된 Expo UI로 SwiftUI·Jetpack Compose를 React에서 선언하는 법. Universal API 우선 전략, 플랫폼별 패키지, drop-in 교체, useNativeState worklet까지 다룹니다.
핵심 요약
- Expo UI는 SwiftUI·Jetpack Compose 네이티브 컴포넌트를 React 코드에서 직접 선언하는 시스템으로, SDK 56부터 두 API 모두 stable로 올라섰습니다.
- 공통 화면은
@expo/uiUniversal API로 먼저 작성하고 플랫폼 고유 동작이 필요한 곳만@expo/ui/swift-ui·@expo/ui/jetpack-compose로 내려갑니다. Host는 Universal 하위 트리의 루트로 Android·iOS는 네이티브 Host, web은 RN View로 fallback합니다.- bottom-sheet·slider·picker 등 커뮤니티 라이브러리는 import만 바꿔 옮기되 prop이 달라질 수 있어 시각·접근성 회귀 테스트를 붙입니다.
- 입력·애니메이션처럼 JS 왕복 지연이 보이는 화면은
useNativeState와 worklet callback을 우선 검토하며, native tree라measure()·onLayout의존 코드는 피합니다.
개요
Expo UI는 SwiftUI(iOS)와 Jetpack Compose(Android)의 네이티브 컴포넌트를 React 코드에서 직접 선언하는 시스템입니다. SDK 56부터 SwiftUI와 Jetpack Compose API가 stable로 올라섰고, Android·iOS·web을 한 API로 감싸는 Universal 컴포넌트도 들어왔습니다.
SDK 56 기준 상태
- SwiftUI 지원: stable
- Jetpack Compose 지원: stable
- Universal API:
@expo/ui루트 import로 Android·iOS·web 공통 UI 작성 - Expo Go 포함: 새 기본 템플릿에서 바로 사용 가능
- AI 통합: Expo Skills와 Expo MCP로 API 최신성 보강
Universal 컴포넌트 우선 전략
SDK 56에서는 공통 화면을 먼저 Universal API로 짜고, 특정 플랫폼 동작이 필요한 곳만
@expo/ui/swift-ui나 @expo/ui/jetpack-compose로 내려가는 방식이 가장 안정적입니다.
import { Button, Column, Host, Switch, Text } from '@expo/ui';
export default function SettingsScreen() {
return (
<Host style={{ flex: 1 }}>
<Column spacing={12} alignment="center">
<Text>알림 설정</Text>
<Switch label="알림 받기" value={enabled} onValueChange={setEnabled} />
<Button label="저장" onPress={handleSave} />
</Column>
</Host>
);
}Host는 Universal Expo UI 하위 트리의 루트입니다. Android·iOS에서는 각 네이티브 Host로 연결되고,
web에서는 React Native View로 fallback합니다.
플랫폼별 API
Universal API가 노출하지 않는 modifier, native state, 세부 컨트롤이 필요하면 플랫폼별 패키지를 사용합니다.
import { Platform } from 'react-native';
const NativeSettings = Platform.select({
ios: () => require('@expo/ui/swift-ui').Form,
android: () => require('@expo/ui/jetpack-compose').Column,
default: () => require('@expo/ui').Column,
})();| 패키지 | 사용 기준 |
|---|---|
@expo/ui | 공통 form, controls, sheet, text, layout |
@expo/ui/swift-ui | iOS 시스템 폼, modifier, SwiftUI 고유 컴포넌트 |
@expo/ui/jetpack-compose | Material 3, Dynamic Color, Compose modifier |
Drop-in 교체 대상
SDK 56은 여러 커뮤니티 네이티브 UI 라이브러리를 import만 바꿔 옮기는 경로를 제공합니다. prop이 다 같지는 않으므로 핵심 화면은 시각·접근성 회귀 테스트를 붙입니다.
| 기존 라이브러리 | Expo UI 방향 |
|---|---|
@gorhom/bottom-sheet | Expo UI BottomSheet |
@react-native-community/datetimepicker | Expo UI Date/Time controls |
@react-native-community/slider | Expo UI Slider |
@react-native-picker/picker | Expo UI Picker |
react-native-pager-view | Expo UI pager 계열 |
@react-native-segmented-control/segmented-control | Expo UI segmented control |
Native state와 worklet callback
SDK 56의 Expo UI는 react-native-worklets와 native state primitive를 통합합니다. 텍스트 입력이나
애니메이션처럼 JS 왕복 지연이 눈에 띄는 화면은 useNativeState와 WorkletCallback을 먼저 검토합니다.
import { Host, TextField, useNativeState } from '@expo/ui/swift-ui';
export function SearchField() {
const value = useNativeState('');
return (
<Host matchContents>
<TextField text={value} placeholder="검색" />
</Host>
);
}AI 통합
Expo는 공식 Expo Skills를 제공하며, expo-ui-swift-ui, expo-ui-jetpack-compose,
building-native-ui 같은 스킬이 최신 API 이름과 권장 패턴을 보강합니다.
npx skills add expo/skills코드 생성 결과는 반드시 npx expo-doctor, TypeScript, 실제 device build로 검증합니다. Expo UI는 네이티브
레이아웃을 그대로 쓰는 만큼 웹 미리보기만으로는 터치 영역·시스템 폰트·Dynamic Color 차이를 잡기 어렵습니다.
주의사항
- Expo UI 컴포넌트는 React Native View와 다른 native tree에 올라가므로
measure()·onLayout의존 코드는 피합니다. - Universal web API는 아직 바뀔 수 있으므로 웹 핵심 플로우는 별도 회귀 테스트를 둡니다.
- native modifier는 플랫폼마다 의미가 다릅니다. 디자인 시스템 토큰은 JS에서 통일하되 구현은 플랫폼별로 검증합니다.
- Drop-in replacement도 prop이 다를 수 있습니다. 접근성 label, keyboard behavior, screen reader 순서를 체크합니다.