OpenAIExpo2026/05/27 13:30

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

要点だけを先に読めるように短く再構成したセクションです。

元記事

Quick Digest

要約

要点だけを先に読めるように短く再構成したセクションです。

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

Expo UIのWorklet統合:SwiftUIとComposeの状態を同期制御

Key Points

  • UIスレッドで同期実行
  • SwiftUI/Composeと状態共有
  • ちらつきなしの入力マスク

Summary

SDK 56でExpo UIはUIランタイムのworkletをネイティブにサポートし、useNativeStateとworkletコールバックを組み合わせることで、UIスレッド上でイベントを同期実行しSwiftUI(iOS)のObservableObject/Compose(Android)のMutableStateと状態を共有できるようになりました。これにより、JSスレッドの往復なしでテキスト入力の更新・整形・再レンダリングを同一フレームで行えます。

必要条件: react-native-reanimatedreact-native-workletsをプロジェクトに追加してください。実装例は@expo/ui/swift-ui@expo/ui/jetpack-composeTextInputで動作します。

Key Points

  • useNativeStateでネイティブ側にObservableState(共有状態)を作成し、iOSはObservableObject、AndroidはMutableStateにマッピングされる。
  • worklet内(先頭に'worklet';)のコールバックはネイティブのUIスレッド上で即時実行され、text.valueを書き換えるとそのフレームでSwiftUI/Composeが再描画される。
  • 主な利点はちらつきのない入力マスキングや同期的なUI更新(例:クレジットカード、電話番号、日付、通貨など)。
  • 実装のヒント: Host matchContentsでラップし、TextInputvalueuseNativeState('')を渡し、onChangeText内で同期処理を行う。
  • 現状はSDK 56で導入済み、TextInputが最初の対応コンポーネント。今後他のフォームコントロールも順次追加予定。

Full Translation

翻訳

原文の流れを保ったまま読める翻訳セクションです。

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 は最初に対応したコンポーネントの一つです。今後のリリースでより多くのフォームコントロールが同期コールバックを持つようになることを期待してください。