Expo UI에서 워크릿으로 SwiftUI와 Compose 상태를 동기화 제어
출처: Dev.to
React Native 개발자들은 오래전부터 JavaScript와 네이티브 UI 스레드 사이를 연결하는 브리징의 마찰을 겪어왔습니다. 네이티브 상태를 업데이트할 때마다 브리지를 통해 메시지를 보내고, 왕복을 기다리며, 사용자가 지연을 눈치채지 않기를 바라는 것이 일상이었습니다. SDK 56의 Expo UI는 워크렛 통합을 통해 이 방식을 바꿉니다.
이제 SwiftUI와 Compose 상태를 UI 스레드에서 직접 제어할 수 있으며, JavaScript 왕복이 전혀 없습니다. 실제 모습은 다음과 같습니다:
import { Host, TextInput, useNativeState } from '@expo/ui';
export default function Screen() {
const value = useNativeState('');
return (
{
'worklet';
// Runs synchronously on the UI thread, on every keystroke.
console.log('[UI thread] typed:', value);
}}
/>
);
}
Note: 이 기능을 사용하려면 프로젝트에
react-native-reanimated와react-native-worklets가 설치되어 있어야 합니다.
실제 동작 방식
이 기능을 가능하게 하는 두 가지 요소가 있습니다:
-
**
useNativeState**는ObservableState—네이티브 코드에 존재하고 SwiftUI와 Compose 모두가 관찰하는SharedObject—를 생성합니다.- iOS:
ObservableObject에 매핑됩니다. - Android:
MutableState에 매핑됩니다.
두 플랫폼 모두 이 상태를 감시하고 변경될 때마다 다시 렌더링합니다.
- iOS:
-
워크렛 콜백(예:
onChangeText)은 네이티브 뷰가 이벤트를 발생시킬 때 UI 스레드에서 직접 실행되어, 브리지를 거치는 과정을 없앱니다.
두 요소를 결합하면: TextInput에서 키를 누를 때마다 공유된 text 상태가 업데이트되고 워크렛이 실행되며, SwiftUI와 Compose가 UI 스레드에서 같은 프레임 안에 다시 렌더링됩니다.
SwiftUI에 익숙하다면 이 패턴이 친숙하게 느껴질 것입니다. 위의 TypeScript 코드는 거의 그대로 Swift 코드로 변환됩니다:
struct Screen: View {
@State var text = ""
var body: some View {
TextField("Type something", text: $text)
.onChange(of: text) { _, newValue in
print("[UI thread] typed:", newValue)
}
}
}
useNativeState는 @State와 같은 역할을 하고, text={text}는 TextField(text: $text)와 동일하게 동작하며, 워크렛 onChangeText는 .onChange(of:)와 같은 동작을 합니다. Compose 개발자라면 mutableStateOf와 onValueChange를 떠올리실 겁니다.
깜박임 없는 입력 마스킹
즉각적인 장점은 실제로 동작하는 입력 마스킹입니다. 워크렛이 키 입력과 같은 프레임에서 text.value를 수정할 수 있기 때문에 사용자는 마스킹되지 않은 문자를 전혀 보지 못합니다—비동기 지연이나 눈에 보이는 교정이 없습니다.
예시: 사용자가 입력할 때 4242424242424242를 4242 4242 4242 4242 형태로 포맷해 주는 신용카드 번호 필드.
import { Host, TextInput, useNativeState } from '@expo/ui/swift-ui';
export default function CardNumberField() {
const text = useNativeState('');
return (
{
'worklet';
const digits = value.replace(/\D/g, '').slice(0, 16);
const masked = digits.replace(/(.{4})/g, '$1 ').trim();
text.value = masked;
}}
/>
);
}
포맷팅은 UI 스레드에서 즉시 이루어집니다. 같은 패턴을 전화번호, 날짜, 통화 포맷팅 등 표시되는 텍스트와 원시 입력이 다를 때 모두 적용할 수 있습니다.
입력 마스킹을 넘어
워크렛 통합은 입력 관련 문제 해결을 넘어서는 역할을 합니다. 기존 비동기 API와 병행해 동기식 대안을 제공함으로써, 인터랙션 요구에 맞는 방식을 선택할 수 있게 해줍니다.
네이티브‑state + 워크렛 패턴은 React Native에서 SwiftUI와 Compose의 상태‑구동 API를 훨씬 더 폭넓게 활용할 수 있게 합니다. 입력 마스킹은 시작에 불과합니다.
시작하기
워크렛 지원은 @expo/ui/swift-ui와 @expo/ui/jetpack-compose 모두에서 동작합니다. SDK 56에 포함되어 배포되었습니다. TextInput이 이제 동기 콜백을 지원하며, 향후 릴리스에서는 더 많은 폼 컨트롤이 추가될 예정입니다.
이 글은 Expo 블로그의 내용을 기반으로 작성되었습니다. 더 많은 React Native 소식을 원한다면 @expo를 팔로우하세요.