OpenAIExpoMay 27, 2026, 1:30 PM

Worklet integration in Expo UI: synchronously controlling SwiftUI and Compose state

A condensed section focused on the key takeaways first.

Original Post

Quick Digest

Summary

A condensed section focused on the key takeaways first.

openaienmodel: gpt-5-mini-2025-08-07

Worklet integration in Expo UI: synchronously controlling SwiftUI and Compose state

Key Points

  • Synchronous UI-thread callbacks
  • Shared native state for SwiftUI and Compose
  • Flicker-free input masking on keystrokes

Summary

SDK 56 adds first-class UI runtime worklet support to Expo UI (via react-native-worklets). You can run event callbacks synchronously on the native UI thread and use useNativeState to create a shared native state observed by both SwiftUI and Jetpack Compose. That lets TextInput callbacks execute and update native state without ever hopping to the JS thread, enabling low-latency interactions like flicker-free input masking.

Key Points

  • API surface: useNativeState, Host, TextInput; worklet callbacks run on the UI thread (use the 'worklet' directive).
  • Native mapping: useNativeState becomes an ObservableObject on iOS and a MutableState on Android; worklets execute directly when the native view fires events.
  • Immediate benefits: synchronous updates and re-renders on keystroke—e.g., you can rewrite text.value in the same frame to implement flicker-free credit-card, phone, date, or currency masking.
  • Requirements and scope: landed in SDK 56; requires react-native-reanimated and react-native-worklets; available in @expo/ui/swift-ui and @expo/ui/jetpack-compose. TextInput is one of the first components wired; more form controls will follow.
  • Guidance for engineers: prefer worklets for low-latency UI tasks and masking; avoid heavy computation on the UI thread and keep complex business logic on JS/native background threads.

Full Translation

Translations

A translation section that keeps the flow of the original article.

openaijamodel: gpt-5-mini-2025-08-07

Expo UI における Worklet 統合:SwiftUI と Compose の状態を同期的に制御する

概要

SDK 56 で、Expo UI は UI ランタイムの worklet と一級の統合を備えています(react-native-worklets による恩恵)。これにより、UI スレッド上でイベントコールバックを同期的に実行し、UI 状態を同期的に制御できます。例えば次のようなコードが可能になります。

import { Host, TextInput, useNativeState } from '@expo/ui';
export default function Screen() {
  const value = useNativeState('');
  return (
    <Host matchContents>
      <TextInput
        value={value}
        placeholder="Type something"
        onChangeText={(value) => {
          'worklet';
          // Runs synchronously on the UI thread, on every keystroke.
          console.log('[UI thread] typed:', value);
        }}
      />
    </Host>
  );
}

注: これを動かすにはプロジェクトに react-native-reanimatedreact-native-worklets がインストールされている必要があります。

実際に何が起きているか

2 つの仕組みが連携しています:

  • useNativeState は ObservableState を作成します。これはネイティブ側に存在する SharedObject で、SwiftUI と Compose の両方から監視されます。内部的には iOS では ObservableObject、Android では MutableState にマッピングされます。
  • onTextChange のような Worklet コールバックは、ネイティブビューがイベントを発火したときに直接 UI スレッド上で実行されます。

これらが組み合わさることで、TextField における各キー入力が共有テキスト状態を更新し、worklet を実行し、SwiftUI と Compose が再レンダリングする、という流れがすべて JS スレッドに切り替わることなく(hop することなく)実現されます。SwiftUI を書いたことがあれば、これが馴染み深い感覚であるはずです。

上の TS コードはほぼ 1:1 で次の SwiftUI コードに対応します:

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) に相当し、worklet の onTextChange.onChange(of:) に対応します。Compose でも mutableStateOfonValueChange で同じ形が成り立ちます。

フリッカーフリーな同期入力マスキング

もっとも分かりやすい利点は、入力マスキングが確実に動作することです。worklet がキー入力が到着した同じフレームで text.value を書き換えられるため、ユーザーは未マスクの文字を目にすることがなく、JS スレッドへの非同期往復も発生しません。例えば、カード番号フィールドで 4242424242424242 をユーザーが入力すると、タイピング中に UI スレッド上で 4242 4242 4242 4242 と整形できます:

import { Host, TextInput, useNativeState } from '@expo/ui/swift-ui';
export default function CardNumberField() {
  const value = useNativeState('');
  return (
    <Host matchContents>
      <TextInput
        value={value}
        placeholder="Card number"
        onChangeText={(value) => {
          'worklet';
          const digits = value.replace(/\D/g, '').slice(0, 16);
          const masked = digits.replace(/(.{4})/g, '$1 ').trim();
          text.value = masked;
        }}
      />
    </Host>
  );
}

同じパターンは電話番号、日付、郵便番号、通貨など、表示テキストと実際の生入力が異なる必要があるあらゆるケースに適用できます。

なぜ worklet 統合が重要か

Worklet 統合により、Expo UI はよりネイティブに近い UX を提供でき、既存の非同期的アプローチに対して同期的な代替手段を得られます。これにより、やり取りの要件に応じて手法を選べます。入力マスキングは一例に過ぎません。ネイティブ状態 + worklet の組み合わせは、SwiftUI や Compose ベースの多くのネイティブ状態 API を React Native に導入する土台にもなります。今後の展開が楽しみです。

試してみてください

  • Worklet サポートは @expo/ui/swift-ui@expo/ui/jetpack-compose の両方で動作し、SDK 56 に導入されています。
  • TextInput は最初に対応したコンポーネントの一つです。今後のリリースでより多くのフォームコントロールが同期コールバックを持つようになることを期待してください。