しくみを知る — UIスレッドとworklet
Reanimatedの滑らかさを支えるUIスレッドとworklet、そして共有値の役割を図とともに理解します。
前章で、Reanimatedはアニメーションの計算をUIスレッドで行うから滑らかなのだ、という話をしました。この章では、その「UIスレッドで動かす」を実現しているworklet(ワークレット)と、値をやりとりする共有値(Shared Value)のしくみを見ていきます。
いきなり内部実装に立ち入るのではなく、まずは全体の見取り図から入りましょう。
2つのスレッド
React Nativeのアプリは、大きく分けて2つのスレッドの上で動いています。ひとつはアプリのJavaScriptコードを実行するJSスレッド(JavaScript thread)、もうひとつは画面の描画を担当するUIスレッド(UI thread)です。
あなたが書くコンポーネントのロジック、状態の更新、イベントハンドラーの多くはJSスレッドで動きます。一方、実際にピクセルを画面に反映する仕事はUIスレッドの担当です。
Reanimatedは、アニメーションのスタイル計算をこのUIスレッド側に置きます。図にすると次のような関係です。

JSスレッドがほかの処理を抱えていても、UIスレッド上のworkletは描画を進められます。
ポイントは、毎フレームのスタイル計算がJSスレッドを経由しない点です。JSスレッドが重い処理でふさがっていても、UIスレッド側のアニメーションはそれとは独立して進みます。これが滑らかさの正体です。
workletとは何か
では、そのUIスレッドで動く計算は、どうやって書くのでしょうか。ここで登場するのがworkletです。
workletは、複数のJavaScriptランタイムをまたいで移動・実行できる、短命なJavaScript関数です。Reanimatedはこのworkletを使って、ビューのスタイルを計算したり、イベントに反応したりする処理をUIスレッド上で走らせます。
普通の関数と何が違うのか。見た目上は、関数の先頭に'worklet'という一行のディレクティブが付くだけです。
function myWorklet() {
'worklet';
console.log('Hello from a worklet');
}
この目印が付いた関数を、Worklets Babelプラグインが見つけ出し、別のスレッドへ持ち運べる形に変換します。この変換のことをworkletize(ワークレット化)と呼びます。
とはいえ、実際のアプリ開発でこのディレクティブを毎回手で書くことはほとんどありません。ReanimatedやGesture Handlerと一緒に使うコードは、たいていの場合自動でworklet化され、UIスレッドで実行されます。たとえば、次章以降で何度も出てくるuseAnimatedStyleに渡す関数は、その中身が自動的にUIスレッドで動くようになっています。
import { useAnimatedStyle } from 'react-native-reanimated';
function App() {
const style = useAnimatedStyle(() => {
// ここはUIスレッドで実行される
return { opacity: 0.5 };
});
}
つまり、多くの場面ではworkletを強く意識しなくても書けるように作られています。それでも「この関数はUIスレッドで動いている」という感覚を持っておくと、後々の理解がぐっと楽になります。
共有値がスレッドをつなぐ
worklet化された計算はUIスレッドで動きます。一方で、コンポーネントの状態やロジックはJSスレッドにあります。この2つのスレッドの間で値を受け渡す橋渡し役が、共有値(Shared Value)です。
共有値は、その名のとおり2つのスレッドの間で同期される値です。JSスレッド側で値を書き換えると、ReanimatedがUIスレッド側にも反映し、workletから新しい値を読めるようにします。この同期のおかげで、JS側のロジックとUI側のアニメーションが自然につながります。
共有値はuseSharedValueフックで作り、中身には.valueプロパティからアクセスします。
import { useSharedValue } from 'react-native-reanimated';
function App() {
const width = useSharedValue(100);
function grow() {
width.value += 50; // JS側から書き換えると、UI側にも反映される
}
}
前章のデモで押した「幅が伸びる四角形」も、この共有値がボタン(JSスレッド)とアニメーション(UIスレッド)をつないでいました。ボタンを押すと共有値が書き換わり、UIスレッド上のworkletがその新しい値をもとに毎フレームの幅を計算する、という流れです。
Webでのふるまい
本書のデモはブラウザ上で動いています。実はWebには独立したUIスレッドがありません。そのためReanimatedがブラウザで動くとき、workletはただのJavaScript関数として解決されます。
それでも、明示的にworkletとして扱いたい関数には'worklet'ディレクティブが必要です。また、依存の検出はWorklets Babelプラグインが担います。本書のデモはBabelプラグインなしのVite上で動いているので、useAnimatedStyleに依存配列を明示する、といった調整を入れています。この違いは、デモのコードを読むときに頭の片隅に置いておいてください。
まだ立ち入らないこと
ここまでで、UIスレッド・worklet・共有値という3つの登場人物と、その関係をつかみました。この土台があれば、第2部の実践的な内容はすんなり読み進められます。
一方で、workletがどのように外側の変数を取り込むか(クロージャ)や、ランタイムをまたぐ実行の細部といった深いテーマには、ここではあえて立ち入りません。これらは第6部「高度なReanimated」でじっくり扱います。今の段階では、しくみの輪郭が見えていれば十分です。