ビデオによる概要説明
Vegaでアプリのパフォーマンスを改善する方法の概要については、次のビデオを参照してください。再レンダリングと再定義の回避
memo
Reactのmemoを使用すると、関数コンポーネントの不要な再レンダリングを防ぎ、パフォーマンを向上させることができます。デフォルトでは、Reactは親コンポーネントが再レンダリングされるたびにコンポーネントを再レンダリングします。これはコンポーネントのプロパティが変更されていなくても行われます。Reactのmemoは、以前のプロパティと新しいプロパティの浅い比較を実行して、等しい場合は再レンダリングをスキップすることでパフォーマンスを最適化します。この最適化は、リストアイテムやボタンなど、頻繁に更新する必要のない安定したプロパティを持つコンポーネントや、レンダリングロジックの負荷が高いUI要素に対して効果的です。memoをuseCallbackおよびuseMemoと組み合わせることで、無駄なレンダリングを最小限に抑え、Reactアプリの効率を高めることもできます。
デフォルトでは、memoはObject.is静的メソッドを使用して、プロパティの浅い比較を実行し、コンポーネントの再レンダリングが必要かどうかを判断します。場合によっては、この比較をカスタマイズするために、独自のarePropsEqual関数を用意することが必要になります。memoは、インラインスタイルや匿名で定義されたプロパティ値を関数コンポーネントに渡すときに使用されることがあります。この場合、プロパティの値としては等しくてもメモリ内では等しくないため、Object.is()による比較は等しくないという結果になります。複雑なオブジェクトや関数をプロパティとして扱っていて、浅い比較では不十分な場合は、比較のカスタマイズが役立ちます。カスタム比較関数を使用すると、コンポーネントを更新するタイミングをより細かく制御でき、深い比較や固有の条件が必要なシナリオでのパフォーマンスが向上します。
React Nativeには、Reactコンポーネントの再レンダリングを調査するために役立つツールとして、react-devtoolsプロファイラー(英語のみ)の [Record why each component rendered while profiling] フラグと、コミュニティパッケージのwhy-did-you-render(WDYR)(英語のみ)の2つがあります。
ユーザー定義の関数コンポーネントは、memo APIでラップすることが最善です。ただし、あらゆる場所でmemoとuseMemoを使用することが効果的とは限りません。memoとuseMemoをいつどのように使用するかについては、Reactの記事のあらゆる場所にmemoを追加すべきか?を参照してください。
Reactコンパイラとeslint-plugin-react-compiler
React 19リリース候補版(RC)では、memo、useMemo、useCallbackを通じて自動的にメモ化を適用する静的Reactコードコンパイラが導入されました。コンパイラは、アプリがReactのルールに従っていること、セマンティックなJavaScriptまたはTypeScriptであること、null許容または省略可能な値やプロパティへのアクセス前にはテストしていることを前提としています。Reactによれば「コンパイラは実験的であり、多くの粗削りな部分がある」ということですが、Amazonでは、ReactコンパイラのESLintプラグインであるeslint-plugin-react-compilerにオンボーディングすることを推奨します。このプラグインには、React 19へのアップグレードは必要ありません。オンボーディングするには、eslint-plugin-react-hooksのインストールを参照してください。
Strictモード
React Nativeアプリをでラップすると、開発中にアプリ内の潜在的な問題を特定するために役立ちます。は追加のチェックを実行し、非推奨のライフサイクルメソッド、安全でない副作用、パフォーマンスの問題やバグにつながる可能性のあるその他の潜在的な問題について警告します。また、意図しない副作用がないことを確認するために、コンポーネントコンストラクタやuseEffectコールバックなどの特定の関数を二重に呼び出します。このデバッグツールは、開発者が問題を早期に捕捉するために役立ち、アプリの安定性と効率性の改善につながります。は本番環境のビルドには影響しませんが、開発中に使用することがベストプラクティスであり、将来のReactやReact Nativeのアップデートに対応できるようにアプリが準備されます。
Suspenseとlazy
Reactのlazyコンポーネントと境界を使用すると、起動時間とアプリ全体のパフォーマンスを大幅に向上させることができます。Reactのlazyは、コード分割を可能にして、コンポーネントの読み込みが必要時にのみ行われるようにします。これにより、初期バンドルサイズが削減され、アプリの初回レンダリングが高速化されます。Reactのは、lazyと連携するために、必要なコンポーネントが読み込まれるまでアプリでレンダリングが「一時停止」されるようにします。これにより、UIを妨げないスムーズなユーザーエクスペリエンスを作成できます。このアプローチでは、アプリの起動時間が最適化されるだけでなく、バンドルの読み込みも効率化され、ビューごとに必要なコードのみが読み込まれます。
useCallback
ReactでuseCallbackを使用すると、関数のインスタンスがメモ化され、レンダリングごとの不要な再作成が防止されるため、パフォーマンスが向上します。useCallbackは、関数をプロパティとして子コンポーネントに渡す場合に効果的です。それらのコンポーネントが参照等価性チェックに依存している場合に、不要な再レンダリングを回避するために役立ちます。たとえば、React.memoでラップされている場合が該当します。useCallbackにより、関数がエフェクトの依存関係である場合(useEffect)に、副作用が意図せず再実行されるのを防ぐことができます。useCallbackは、過度に使用するとメモリ使用量と複雑さが増す可能性があるため、慎重に使用してください。useCallbackを正しく使用すると、Reactアプリのレンダリング効率が最適化され、パフォーマンスのオーバーヘッドが削減されます。
useCallbackの依存配列には項目を追加して、メモ化された関数が、参照しているリアクティブな依存関係の最新の値にアクセスできるようにします。依存関係を省略すると、クロージャが古くなり、以前のレンダリングからの古い値が関数で使用され続けて、予期しない動作やバグが発生する可能性があります。
useEffect()
React Nativeでの開発に限りませんが、ReactのuseEffectをどのように使用するかがパフォーマンスに影響する可能性があります。Reactの記事の「そのエフェクトは不要かも」が参考になります。useEffectの依存配列には常に、すべてのリアクティブな値(コンポーネントの本体で宣言されているプロパティ、状態、変数、関数など)を含めるようにしてください。eslint-plugin-react-compilerを使用すると、useEffectの依存配列に不足している依存関係を検出できます。
useMemo
ReactのuseMemoは、コストの高い計算をメモ化し、レンダリングのたびに不要な再計算が行われるのを防ぐことでパフォーマンスを高めます。useMemoを使用しないと、大規模なリストのフィルタリング、数学的な計算の実行、データの処理など、複雑な操作を行う関数がレンダリングのたびに実行されることになります。これらの関数は、その依存関係に変更がなくても実行されます。このような計算をuseMemoでラップすると、Reactは、その依存関係が更新されたときにのみ再評価を実行します。これにより、CPU使用率が低減され、応答性が向上します。useMemoは、関係のない状態変更が原因でコンポーネントが頻繁に再レンダリングされるシナリオで使用すると効果的です。useMemoは場面を選んで使用してください。過度に使用すると、不要なメモリオーバーヘッドが追加される可能性があります。
メモ化された値を依存関係の変更時に再計算するには、useMemoの依存配列に項目を追加します。依存関係を省略すると、値が古くなったり不正確になったりする場合があります。一方、不要な依存関係は不要な再計算を引き起こし、パフォーマンスの低下につながる可能性があります。
useStateとuseTransition
ReactのuseStateの使用を最小限に抑えると、不要な再レンダリングが減り、パフォーマンスが向上する可能性があります。再レンダリングは状態の更新のたびにトリガーされるため、過剰な状態管理はパフォーマンスのボトルネックになることがあります。これは特に、複雑なコンポーネントに当てはまります。useStateの使用頻度を減らすと、コンポーネントの効率を高めることができます。useStateの使用を抑えるには、レンダリングに影響しない可変値に参照(useRef)を使用したり、冗長な値を保持する代わりに状態を引き継いだりする方法があります。useStateを避けることで、コンポーネントをより効率化できます。useTransitionフックは、ユーザー入力などの緊急の状態更新に優先順位を付けることでパフォーマンスを高めます。同時に、検索結果などの重要度の低い状態更新は延期されます。useTransitionを使用するとUIのブロックも防止され、コストの高い状態遷移中でも対話操作の応答性が維持されるため、よりスムーズなエクスペリエンスを実現できます。状態設定の呼び出しをuseTransitionからのstartTransition関数でラップすると、Reactは、古い状態更新のレンダリングを中断して最新の状態更新のレンダリングを優先できるようになります。
サンプルアプリ
最適化前
最適化後
大規模なリストの処理方法
React Nativeには、多数のさまざまなリストコンポーネントがあります。ScrollView
パフォーマンスの観点では、通常、ScrollViewコンポーネントよりもFlatListコンポーネントを使用する方が良い結果が得られます。FlatListでは、仮想化によって子コンポーネントのレンダリングが遅延実行されるためです。ScrollViewコンポーネントでは、すべてのReact子コンポーネントが一度にレンダリングされます。そのため、ScrollViewコンポーネントで多数の子コンポーネントを使用すると、パフォーマンスに影響が及びます。レンダリングが低速になり、メモリ使用量が増加します。ただし、データセットが小さいリストでは、ScrollViewを使用してもまったく問題はありません。ScrollViewとその子を適切にメモ化することを忘れないようにしてください。
FlatList
FlatList(英語のみ)の必須プロパティであるdataとrenderItemを設定しただけでは、FlatListコンポーネントを使用するパフォーマンス上の利点がすべて有効になるわけではありません。アプリの滑らかさ(FPS)、CPU使用率、メモリ使用量を改善するには、FlatListコンポーネントのほかのプロパティを設定します。React Nativeの公式記事のOptimizing FlatList Configuration(英語のみ)で説明されているベストプラクティスを適用してください。Amazonでのテストから、滑らかさとCPU使用率には、getItemLayout、windowSize、initialNumToRenderの各プロパティに加えて、子コンポーネントにメモ化を適用する(英語のみ)ことが重要であると判明しました。アプリで垂直スクロールと水平スクロールをサポートするためにFlatListを入れ子にする場合は、入れ子になったFlatListコンポーネントをメモ化する必要があります。
FlashList
Vegaでは、ShopifyのFlashListパッケージ(英語のみ)が完全にサポートされています。これはReact NativeのFlatListコンポーネントを置き換えることができ、より高いパフォーマンスを実現します。どちらも同じコンポーネントプロパティを使用しているため、FlatListからFlashListへの置き換えは簡単です。ただし、FlatListに固有の一部のプロパティは、FlashListでは機能しなくなります。このようなプロパティには、windowSize、getItemLayout, initialNumToRender、maxToRenderPerBatch、updateCellsBatchingPeriodなどがあります。完全な一覧は、使用方法に関する記事(英語のみ)の最後に記載されています。FlashListのパフォーマンスを向上させるには、いくつかの重要なプロパティを設定し、以下のベストプラクティスに従ってください。
- まず、
FlashListにestimatedItemSizeプロパティを設定します。FlashListでestimatedItemSizeプロパティを使用すると、適切な数のアイテムの事前レンダリングが可能になり、空白領域の発生や読み込み時間が最小限に抑えられるため、パフォーマンスの向上につながります。また、不要な再レンダリングや大規模なレンダリングツリーが回避され、高速スクロール時の応答性も向上します。詳細については、Estimated Item Size Prop(英語のみ)を参照してください。 FlashListでは、アイテムのリサイクルにより、画面外のコンポーネントが破棄されずに再利用され、不要な再レンダリングが防止され、メモリ使用量が削減されます。これがパフォーマンスの向上に寄与しています。このリサイクルを最適化するために、リサイクルされるコンポーネントの動的プロパティにはuseStateを使用しないでください。使用すると、以前のアイテムの状態の値が保持されて効率が悪くなります。詳細については、Recycling(英語のみ)を参照してください。- アイテムコンポーネントおよびその下に入れ子になったコンポーネントから、keyプロパティを削除します。詳細については、Remove key prop(英語のみ)を参照してください。
- 使用しているセルコンポーネントの型が複数あり、それらの違いが大きい場合は、
getItemTypeプロパティを利用することを検討してください。詳細については、getItemType(英語のみ)を参照してください。
Imageコンポーネント
iOSおよびAndroid上のReact NativeのImageコンポーネントには、キャッシュのようなすぐに使用できるパフォーマンス最適化機能は用意されていません。React Nativeアプリで使用される画像をメモリレベルまたはディスクレベルでキャッシュする場合、React Native開発者は通常、react-native-fast-image(英語のみ)やexpo-image(英語のみ)などのコミュニティパッケージを使用します。Vega向けReact Nativeでは、Imageコンポーネントのネイティブ実装全体にキャッシュメカニズムが組み込まれています。これはreact-native-fast-imageやexpo-imageのImageと同じように動作します。
ユースケースに合わせて、同じ画像アセットを複数のサイズと解像度で用意することをお勧めします。たとえば、FlatListコンポーネントやScrollViewコンポーネント内でImageコンポーネントをレンダリングする場合は、トリミングしたバージョンまたはサムネイルのサイズのアセット(英語のみ)を使用する方が適しています。トリミングしたアセットやサムネイルサイズのアセットでは、画像のデコードに費やされるCPUサイクルが少なくなり、未加工の画像アセットに使用されるメモリ量も減少します。
Animatedライブラリ
React Nativeには、React Nativeのコアコンポーネントで滑らかなアニメーションを実現するAnimatedライブラリ(英語のみ)が用意されています。React Nativeでのアニメーションはコストの高い操作です。フレームごとに、JSスレッドはアニメーションの更新を計算し、ブリッジ経由でネイティブ側に送信してフレームを生成します。アニメーションを使用すると、同時に実行されているほかのプロセスも影響を受ける可能性があります。その結果、JSスレッドが過負荷になり、タイマーの処理、Reactフックの実行、コンポーネントの更新、その他の処理ができなくなることがあります。JSスレッドに過負荷がかかるのを回避するには、アニメーションのuseNativeDriverプロパティをtrueに設定します。useNativeDriverは、ネイティブアニメーションではtrueに設定され、useNativeDriverJSアニメーションではfalseに設定されます。詳細については、Using the native driver(英語のみ)を参照してください。
また、InteractionManager(英語のみ)を使用してアニメーションをスケジュールすることをお勧めします。InteractionManager.runAfterInteractions()は、コールバック関数またはPromiseTaskオブジェクトを受け取り、現在のアニメーションやその他の対話操作が完了した後にのみ実行されるようにします。新しくスケジュールされたアニメーションの実行を遅らせると、JSスレッドが過負荷になる可能性が減り、アプリの応答性と滑らかさが向上します。
フォーカス管理UIの最適化
コンポーネントがフォーカスを受け取ると、onFocusコールバックが呼び出されます。コンポーネントがフォーカスを失うと、onBlurコールバックが呼び出されます。これが、ユーザーがアプリ内を移動したときにUIの変更を行うための手段となります。onFocusとonBlurのハンドラー関数はできるだけシンプルにしてください。これらのハンドラー関数を簡素化するには、いくつかの方法があります。
フォーカスのあるアイテムの周囲に境界線を描画するコンポーネントでは、条件付きスタイルを使用します。たとえば、styles={isFocused ? styles.focusedStyle : styles.blurredStyle}のように指定できます。onFocusとonBlurの呼び出しの組み合わせごとに発生するReactレンダリングサイクルは、最大でも1回に抑えるようにします。これにより、JSスレッドで実行される作業量が最小限になり、ユーザーナビゲーションの応答性が維持されます。
コンポーネントがフォーカスを受け取ったときに複雑なUIの更新が必要な場合は、JSスレッドですべての処理を行わなくて済むように、ネイティブアニメーションを使用します。このようなUI更新の例として、選択されたビュー、テキスト、画像の拡大や、不透明度の変更があります。さらに最適化するには、デバウンスメカニズムを追加して、ユーザーが一定時間アイテムにフォーカスを置いた状態にした場合にのみこのネイティブアニメーションを開始します。デバウンスメカニズムを使用すると、アニメーションはわずかに遅延しますが、ユーザーがアプリ内をすばやく移動したときに、フォーカスを受け取ったすべてのアイテムでアニメーションが開始されるのを防ぐことができます。
リスナー、イベントサブスクリプション、タイマー
VegaAppState、DeviceInfo、Keyboardなどの一部のターボモジュール(TM)では、特定のイベントにリスナーを登録できます。これらのリスナーはuseEffectフック内で作成される場合があります。無効なメモリが発生するのを防ぐために、useEffectフックのreturn関数内でリスナーをクリーンアップしてください。
オーバードローの削減
Vega向けReact Nativeアプリはキャンバスのようなものです。同じスペースに背景色の異なるビューが入れ子に配置されている場合は、同じ領域を何度も塗り重ねることになります。たとえば、全画面のグレーのビューが全画面の黒いビューを覆っている場合、黒いレイヤーは完全に「オーバードロー」されます。システムはそれを処理しますが、目に見えません。オーバードローによっては許容できるものもあり、特に部分的なオーバードローは許容されますが、このような冗長な操作を最小限に抑えるとパフォーマンスが向上します。目標は、フレームごとの各ピクセルの描画回数をできるだけ少なくすることです。アプリを実行するときに起動クエリ引数?SHOW_OVERDRAWN=trueを指定すると、オーバードローを検出できます。
- 実際の色:オーバードローなし
- 青:オーバードローが1回発生。
- 緑:オーバードローが2回発生。
- ピンク:オーバードローが3回発生。
- 赤:オーバードローが4回以上発生。
バンドルサイズの削減
どのようなReactアプリでも、メモリ使用量を最適化し、アプリの起動時間を短縮するには、バンドルサイズを最小限に抑えることが重要です。react-native-bundle-visualizer(英語のみ)は、コストの高いimportステートメントや使用されない依存関係を特定するために役立ちます。インストール後に次のコマンドを実行すると、ブラウザで自動的にHTMLファイルが開き、インタラクティブに確認できます。
lodash via import { debounce } from 'lodash';というステートメントにより、lodashからdebounceを使用しようとしています。このimportステートメントの後、バンドルは次のように視覚化されます。
上の画像から、lodashは493.96KB(17.8%)であることがわかります。このimportステートメントをimport debounce from 'lodash/debounce';に切り替えると、バンドルサイズは大幅に小さくなります。
上の画像では、lodashはわずか13.19KB(0.6%)になっています。このimportステートメントの変更により、アプリのバンドルサイズが480.77KB削減されました。
アプリの初回起動時間の短縮
スプラッシュ画面を効率的に表示するには、VegaのネイティブのSplashScreen APIを使用します。このAPIは、vpkgにバンドルされた未加工の画像アセットからスプラッシュ画面をネイティブにレンダリングするため、アプリの起動時にすぐにスプラッシュ画面が表示されます。このアプローチにより、JavaScriptベースのスプラッシュ画面をレンダリングする代わりに、コンテンツの読み込みやネットワーク呼び出しなど、重要なタスクを処理するためにアプリのJavaScriptスレッドを使用できるようになります。ネイティブのスプラッシュ画面は、アプリの最初のフレームがレンダリングされた後に自動的に閉じられます。スプラッシュ画面を閉じるタイミングをカスタマイズするには、2つのメソッドを使用します。usePreventHideSplashScreen()を呼び出して、スプラッシュ画面の自動消去をオーバーライドします。さらにuseHideSplashScreenCallback()を呼び出して、スプラッシュ画面を非表示にします。
関連トピック
- アプリのKPIを測定
- Vega ESLintプラグインを使用してパフォーマンスの問題を検出する方法
- WebViewアプリを開発する場合は、Vegaウェブアプリパフォーマンスに関するベストプラクティスを参照してください。
- AI活用開発に向けたAmazon Devices Builder Toolsのセットアップ

