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

JavaScriptの読み込みの最適化

JavaScriptコードの解析と実行にはメモリと時間が必要です。そのため、アプリが成長するにつれて、コードを初めて必要になるまで読み込みを遅延させると便利なことがよくあります。React Nativeにはデフォルトで有効になっている標準的な最適化がいくつかあり、Reactがアプリをより効率的にロードするのに役立つテクニックを独自のコードに採用できます。また、非常に大きなアプリに適した高度な自動最適化(独自のトレードオフあり)もいくつかあります。

Hermesは、新しいReact Nativeアプリのデフォルトエンジンであり、効率的なコード読み込みのために高度に最適化されています。リリースビルドでは、JavaScriptコードは事前にバイトコードに完全にコンパイルされます。バイトコードは必要に応じてオンデマンドでメモリにロードされ、プレーンなJavaScriptのように解析する必要はありません。

情報

React NativeでHermesを使用する方法について詳しくは、こちらをご覧ください。

多くのコード/依存関係を持つコンポーネントがアプリの初期レンダリング時に使用される可能性が低い場合は、Reactのlazy APIを使用して、最初にレンダリングされるまでコードの読み込みを延期できます。通常、アプリの画面レベルのコンポーネントを遅延ロードすることを検討する必要があります。これにより、アプリに新しい画面を追加しても起動時間が長くなることはありません。

情報

コード例を含む、Suspenseを使用したコンポーネントの遅延ロードについては、Reactのドキュメントをご覧ください。

ヒント:モジュールの副作用を避ける

コンポーネントモジュール(またはその依存関係)に、グローバル変数の変更やコンポーネント外のイベントへのサブスクライブなど、副作用がある場合、コンポーネントの遅延ロードはアプリの動作を変更する可能性があります。Reactアプリのほとんどのモジュールには副作用があってはなりません。

SideEffects.tsx
import Logger from './utils/Logger';

// 🚩 🚩 🚩 Side effect! This must be executed before React can even begin to
// render the SplashScreen component, and can unexpectedly break code elsewhere
// in your app if you later decide to lazy-load SplashScreen.
global.logger = new Logger();

export function SplashScreen() {
// ...
}

高度:requireをインラインで呼び出す

lazyや非同期import()を使用せずに、初めて使用するまでコードの読み込みを延期したい場合があります。これを行うには、ファイルの先頭で静的なimportを使用する代わりに、require()関数を使用します。

VeryExpensive.tsx
import {Component} from 'react';
import {Text} from 'react-native';
// ... import some very expensive modules

export default function VeryExpensive() {
// ... lots and lots of rendering logic
return <Text>Very Expensive Component</Text>;
}
Optimized.tsx
import {useCallback, useState} from 'react';
import {TouchableOpacity, View, Text} from 'react-native';
// Usually we would write a static import:
// import VeryExpensive from './VeryExpensive';

let VeryExpensive = null;

export default function Optimize() {
const [needsExpensive, setNeedsExpensive] = useState(false);
const didPress = useCallback(() => {
if (VeryExpensive == null) {
VeryExpensive = require('./VeryExpensive').default;
}

setNeedsExpensive(true);
}, []);

return (
<View style={{marginTop: 20}}>
<TouchableOpacity onPress={didPress}>
<Text>Load</Text>
</TouchableOpacity>
{needsExpensive ? <VeryExpensive /> : null}
</View>
);
}

高度:require呼び出しを自動的にインライン化する

React Native CLIを使用してアプリをビルドする場合、require呼び出し(importではない)は、コードと使用するサードパーティパッケージ(node_modules)の両方で自動的にインライン化されます。

import {useCallback, useState} from 'react';
import {TouchableOpacity, View, Text} from 'react-native';

// This top-level require call will be evaluated lazily as part of the component below.
const VeryExpensive = require('./VeryExpensive').default;

export default function Optimize() {
const [needsExpensive, setNeedsExpensive] = useState(false);
const didPress = useCallback(() => {
setNeedsExpensive(true);
}, []);

return (
<View style={{marginTop: 20}}>
<TouchableOpacity onPress={didPress}>
<Text>Load</Text>
</TouchableOpacity>
{needsExpensive ? <VeryExpensive /> : null}
</View>
);
}
情報

一部のReact Nativeフレームワークでは、この動作が無効になっています。特に、Expoプロジェクトでは、require呼び出しはデフォルトではインライン化されません。この最適化を有効にするには、プロジェクトのMetro設定を編集し、getTransformOptionsinlineRequires: trueを設定します。

インラインrequireの落とし穴

require呼び出しをインライン化すると、モジュールが評価される順序が変わり、モジュールが決して評価されないことさえあります。JavaScriptモジュールは副作用がないように記述されることが多いため、これは通常、自動的に行っても安全です。

モジュールの1つに副作用がある場合(たとえば、何らかのログメカニズムを初期化したり、コードの残りの部分で使用されるグローバルAPIをパッチしたりする場合)、予期しない動作やクラッシュが発生する可能性があります。そのような場合は、特定のモジュールをこの最適化から除外するか、完全に無効にすることをお勧めします。

require呼び出しのすべての自動インライン化を無効にするには:

metro.config.jsを更新して、inlineRequiresトランスフォーマーオプションをfalseに設定します。

metro.config.js
module.exports = {
transformer: {
async getTransformOptions() {
return {
transform: {
inlineRequires: false,
},
};
},
},
};

特定のモジュールのみをrequireのインライン化から除外するには:

関連するトランスフォーマーオプションは2つあります。inlineRequires.blockListnonInlinedRequiresです。それぞれを使用する方法の例については、コードスニペットを参照してください。

metro.config.js
module.exports = {
transformer: {
async getTransformOptions() {
return {
transform: {
inlineRequires: {
blockList: {
// require() calls in `DoNotInlineHere.js` will not be inlined.
[require.resolve('./src/DoNotInlineHere.js')]: true,

// require() calls anywhere else will be inlined, unless they
// match any entry nonInlinedRequires (see below).
},
},
nonInlinedRequires: [
// require('react') calls will not be inlined anywhere
'react',
],
},
};
},
},
};

インラインrequireの設定と微調整の詳細については、MetroのgetTransformOptionsのドキュメントを参照してください。

高度:ランダムアクセスのモジュールバンドルを使用する(Hermes以外)

情報

Hermesを使用している場合はサポートされていません。 HermesのバイトコードはRAMバンドル形式と互換性がなく、すべてのユースケースで同じ(またはそれ以上)のパフォーマンスを提供します。

ランダムアクセスのモジュールバンドル(RAMバンドルとも呼ばれます)は、上記のテクニックと連携して、解析してメモリにロードする必要があるJavaScriptコードの量を制限します。各モジュールは、モジュールを実行する必要がある場合にのみ解析される個別の文字列(またはファイル)として格納されます。

RAMバンドルは、物理的に別々のファイルに分割される場合もあれば、単一のファイル内の複数のモジュールのルックアップテーブルで構成される、インデックス付き形式を使用する場合もあります。

AndroidでRAM形式を有効にするには、android/app/build.gradleファイルを編集します。apply from: "../../node_modules/react-native/react.gradle"の行の前に、project.ext.reactブロックを追加または修正します。

project.ext.react = [
bundleCommand: "ram-bundle",
]

単一のインデックス付きファイルを使用する場合は、Androidで次の行を使用します

project.ext.react = [
bundleCommand: "ram-bundle",
extraPackagerArgs: ["--indexed-ram-bundle"]
]

RAMバンドルの設定と微調整の詳細については、MetroのgetTransformOptionsのドキュメントを参照してください。