メインコンテンツへスキップ

新アーキテクチャについて

2018年以来、React Nativeチームは、開発者がより高品質な体験を創造できるよう、React Nativeのコア内部を再設計してきました。2024年現在、このバージョンのReact Nativeは大規模な展開で実績を上げ、Metaのプロダクションアプリを動かしています。

新しいアーキテクチャ」という用語は、新しいフレームワークアーキテクチャと、それをオープンソースにもたらすための作業の両方を指します。

新しいアーキテクチャは、React Native 0.68から実験的にオプトイン可能となっており、その後のすべてのリリースで改善が続けられています。現在、チームはこの新しいアーキテクチャをReact Nativeオープンソースエコシステムにおけるデフォルトのエクスペリエンスとすることに取り組んでいます。

なぜ新しいアーキテクチャが必要なのか?

React Nativeを長年開発してきた中で、チームは開発者が特定の体験を高い完成度で作成することを妨げる一連の制限を特定しました。これらの制限はフレームワークの既存の設計に根本的に起因していたため、新しいアーキテクチャはReact Nativeの未来への投資として始まりました。

新しいアーキテクチャは、従来のアーキテクチャでは不可能だった機能と改善を可能にします。

同期レイアウトとエフェクト

適応性の高いUIエクスペリエンスを構築するには、ビューのサイズと位置を測定し、レイアウトを調整する必要があることがよくあります。

現在、ビューのレイアウト情報を取得し、調整を行うにはonLayoutイベントを使用します。しかし、onLayoutコールバック内の状態更新は、以前のレンダリングの描画後に適用される場合があります。これは、初期レイアウトのレンダリングとレイアウト測定への応答の間に、ユーザーが中間状態や視覚的なジャンプを目にする可能性があることを意味します。

新しいアーキテクチャでは、レイアウト情報に同期的にアクセスし、適切なタイミングで更新をスケジュールすることで、この問題を完全に回避できます。これにより、ユーザーには中間状態が表示されません。

例:ツールチップのレンダリング

ビューの上にツールチップを測定して配置することで、同期レンダリングが何をもたらすかを示すことができます。ツールチップは、どこにレンダリングすべきかを決定するために、ターゲットビューの位置を知る必要があります。

現在のアーキテクチャでは、onLayoutを使用してビューの測定値を取得し、そのビューの位置に基づいてツールチップの位置を更新します。

jsx
function ViewWithTooltip() {
// ...

// We get the layout information and pass to ToolTip to position itself
const onLayout = React.useCallback(event => {
targetRef.current?.measureInWindow((x, y, width, height) => {
// This state update is not guaranteed to run in the same commit
// This results in a visual "jump" as the ToolTip repositions itself
setTargetRect({x, y, width, height});
});
}, []);

return (
<>
<View ref={targetRef} onLayout={onLayout}>
<Text>Some content that renders a tooltip above</Text>
</View>
<Tooltip targetRect={targetRect} />
</>
);
}

新しいアーキテクチャでは、useLayoutEffectを使用して、単一のコミットでレイアウト更新を同期的に測定および適用し、視覚的な「ジャンプ」を回避できます。

jsx
function ViewWithTooltip() {
// ...

useLayoutEffect(() => {
// The measurement and state update for `targetRect` happens in a single commit
// allowing ToolTip to position itself without intermediate paints
targetRef.current?.measureInWindow((x, y, width, height) => {
setTargetRect({x, y, width, height});
});
}, [setTargetRect]);

return (
<>
<View ref={targetRef}>
<Text>Some content that renders a tooltip above</Text>
</View>
<Tooltip targetRect={targetRect} />
</>
);
}
A view that is moving to the corners of the viewport and center with a tooltip rendered either above or below it. The tooltip is rendered after a short delay after the view moves
ToolTipの非同期測定とレンダリング。コードを見る
A view that is moving to the corners of the viewport and center with a tooltip rendered either above or below it. The view and tooltip move in unison.
ToolTipの同期測定とレンダリング。コードを見る

同時レンダラーと機能のサポート

新しいアーキテクチャは、React 18以降で提供されている同時レンダリングと機能をサポートしています。データフェッチのためのSuspense、Transitions、その他の新しいReact APIなどの機能をReact Nativeコードで使用できるようになり、WebとネイティブのReact開発間でコードベースと概念がさらに統合されます。

同時レンダラーは、自動バッチ処理などのすぐに使える改善ももたらし、Reactでの再レンダリングを削減します。

例:自動バッチ処理

新しいアーキテクチャでは、React 18レンダラーによる自動バッチ処理が利用できます。

この例では、スライダーがレンダリングするタイルの数を指定します。スライダーを0から1000にドラッグすると、状態更新と再レンダリングが連続して発生します。

同じコードのレンダラーを比較すると、レンダラーがよりスムーズなUIを提供し、中間UIの更新が少ないことが視覚的にわかります。このネイティブスライダーコンポーネントのような、ネイティブイベントハンドラーからの状態更新もバッチ処理されます。

A video demonstrating an app rendering many views according to a slider input. The slider value is adjusted from 0 to 1000 and the UI slowly catches up to rendering 1000 views.
従来のレンダラーによる頻繁な状態更新のレンダリング。
A video demonstrating an app rendering many views according to a slider input. The slider value is adjusted from 0 to 1000 and the UI resolves to 1000 views faster than the previous example, without as many intermediate states.
React 18レンダラーによる頻繁な状態更新のレンダリング。

Transitionsのような新しい同時機能は、UI更新の優先度を表現する能力を与えます。更新を低優先度としてマークすることで、Reactは、より重要な場所で応答性の高いユーザーエクスペリエンスを確保するために、高優先度の更新を処理するためにレンダリングを「中断」できることを示します。

例:startTransitionの使用

前の例を拡張して、トランジションが進行中のレンダリングを中断して新しい状態更新を処理できる方法を示すことができます。

タイル番号の状態更新をstartTransitionでラップし、タイルのレンダリングを中断できることを示します。startTransitionは、トランジションが完了したかどうかを示すisPendingフラグも提供します。

jsx
function TileSlider({value, onValueChange}) {
const [isPending, startTransition] = useTransition();

return (
<>
<View>
<Text>
Render {value} Tiles
</Text>
<ActivityIndicator animating={isPending} />
</View>
<Slider
value={1}
minimumValue={1}
maximumValue={1000}
step={1}
onValueChange={newValue => {
startTransition(() => {
onValueChange(newValue);
});
}}
/>
</>
);
}

function ManyTiles() {
const [value, setValue] = useState(1);
const tiles = generateTileViews(value);
return (
<TileSlider onValueChange={setValue} value={value} />
<View>
{tiles}
</View>
)
}

トランジションで頻繁な更新があっても、Reactは古い状態になるとすぐにレンダリングを中止するため、中間状態のレンダリングが少なくなることに気づくでしょう。比較として、トランジションがない場合は、より多くの中間状態がレンダリングされます。どちらの例も引き続き自動バッチ処理を使用しますが、トランジションは、進行中のレンダリングをバッチ処理するさらに強力な機能開発者に提供します。

A video demonstrating an app rendering many views (tiles) according to a slider input. The views are rendered in batches as the slider is quickly adjusted from 0 to 1000. There are less batch renders in comparison to the next video.
古い状態の進行中のレンダリングを中断するためにトランジションを使用してタイルをレンダリングする。コードを見る
A video demonstrating an app rendering many views (tiles) according to a slider input. The views are rendered in batches as the slider is quickly adjusted from 0 to 1000.
トランジションとしてマークせずにタイルをレンダリングする。コードを見る

高速なJavaScript/ネイティブインターフェース

新しいアーキテクチャは、JavaScriptとネイティブ間の非同期ブリッジを削除し、JavaScriptインターフェース(JSI)に置き換えます。JSIは、JavaScriptがC++オブジェクトへの参照を保持し、その逆も可能にするインターフェースです。メモリ参照を使用すると、シリアル化コストなしでメソッドを直接呼び出すことができます。

JSIは、React Nativeで人気のカメラライブラリであるVisionCameraがリアルタイムでフレームを処理することを可能にします。一般的なフレームバッファは約30MBで、フレームレートに応じて1秒あたり約2GBのデータに相当します。ブリッジのシリアル化コストと比較して、JSIはこれほどのインターフェースデータを簡単に処理します。JSIは、データベース、画像、オーディオサンプルなどの他の複雑なインスタンスベースのタイプを公開できます。

新しいアーキテクチャでのJSIの採用により、すべてのネイティブとJavaScriptの相互運用からこの種のシリアル化作業が削除されます。これには、ViewTextのようなネイティブコアコンポーネントの初期化と再レンダリングが含まれます。新しいアーキテクチャにおけるレンダリングパフォーマンスの調査と測定されたベンチマークの改善について、詳しく読むことができます。

新しいアーキテクチャを有効にすると何が期待できるか?

新しいアーキテクチャはこれらの機能と改善を可能にしますが、アプリやライブラリで新しいアーキテクチャを有効にしても、すぐにパフォーマンスやユーザーエクスペリエンスが向上するとは限りません。

たとえば、同期レイアウトエフェクトや同時機能のような新しい機能を活用するには、コードのリファクタリングが必要になる場合があります。JSIはJavaScriptとネイティブメモリ間のオーバーヘッドを最小限に抑えますが、データシリアル化がアプリのパフォーマンスのボトルネックではなかったかもしれません。

アプリまたはライブラリで新しいアーキテクチャを有効にすることは、React Nativeの未来を選択することです。

チームは、新しいアーキテクチャが解き放つ新しい機能を積極的に研究・開発しています。たとえば、Webとの連携はMetaで活発に探索されている領域であり、React Nativeオープンソースエコシステムにも提供される予定です。

専用のディスカッション&プロポーザルリポジトリでフォローし、貢献することができます。

今日、新しいアーキテクチャを使うべきか?

0.76からは、すべてのReact Nativeプロジェクトで新しいアーキテクチャがデフォルトで有効になっています。

もし何かうまくいかない点があれば、このテンプレートを使用してイシューを開いてください。

何らかの理由で新しいアーキテクチャを使用できない場合でも、オプトアウトすることができます。

Android

  1. android/gradle.propertiesファイルを開く
  2. newArchEnabledフラグをtrueからfalseに切り替える
gradle.properties
# Use this property to enable support to the new architecture.
# This will allow you to use TurboModules and the Fabric render in
# your application. You should enable this flag either if you want
# to write custom TurboModules/Fabric components OR use libraries that
# are providing them.
-newArchEnabled=true
+newArchEnabled=false

iOS

  1. ios/Podfileファイルを開く
  2. PodfileのメインスコープにENV['RCT_NEW_ARCH_ENABLED'] = '0'を追加します(テンプレートの参照Podfile
diff
+ ENV['RCT_NEW_ARCH_ENABLED'] = '0'
# Resolve react_native_pods.rb with node to allow for hoisting
require Pod::Executable.execute_command('node', ['-p',
'require.resolve(
  1. 次のコマンドでCocoaPodsの依存関係をインストールします。
shell
bundle exec pod install