FlatListパフォーマンス改善の秘訣:initialNumToRender以外の最適化手法

2025-05-31

initialNumToRender とは何か?

このプロパティは、FlatList が最初にマウントされたときに、画面上に表示する(または、表示されうる)アイテムの数を指定します。つまり、ユーザーがリストをスクロールする前に、どのくらいの数のアイテムを事前にレンダリングしておくかを決めるものです。

なぜこれが重要なのか?

  1. パフォーマンスの向上:

    • FlatList は大量のデータを扱う際に非常に効率的ですが、すべてのアイテムを一度にレンダリングしようとすると、特にアイテム数が多い場合や各アイテムが複雑なコンポーネントである場合に、アプリケーションの起動時やリストの表示時にパフォーマンスの問題(カクつきや遅延)が発生する可能性があります。
    • initialNumToRender を適切に設定することで、最初に必要最小限のアイテムのみをレンダリングし、それ以外のアイテムはユーザーがスクロールして表示領域に入ったときに「遅延ロード」されます。これにより、初期ロード時間を短縮し、よりスムーズなユーザー体験を提供できます。
  2. メモリ使用量の削減:

    • 一度にレンダリングされるコンポーネントの数を制限することで、アプリケーションが使用するメモリ量を削減できます。これは、リソースが限られているモバイルデバイスにおいては特に重要です。

どのように設定するのか?

initialNumToRenderFlatList コンポーネントのプロパティとして数値で指定します。

<FlatList
  data={myData}
  renderItem={({ item }) => <MyListItem item={item} />}
  keyExtractor={item => item.id}
  initialNumToRender={10} // 例: 最初の10個のアイテムをレンダリングする
/>

適切な値の選び方

initialNumToRender の最適な値は、アプリケーションの要件やリストの表示方法によって異なります。

  • テストと調整: 実際に様々なデバイスでテストを行い、アプリケーションのパフォーマンスを監視しながら最適な値を見つけることが重要です。
  • 「空白」の回避: 値が小さすぎると、ユーザーが少しスクロールしただけで、まだレンダリングされていない「空白」が表示されてしまう可能性があります。これを避けるためには、画面に表示されるアイテム数よりも少し多めに設定することを検討してください。
  • 画面に収まるアイテム数: ユーザーがスクロールせずに一度に画面に表示されるであろうアイテムの数を基準に設定するのが一般的です。例えば、スマートフォンで5〜7個のアイテムが表示される場合、initialNumToRender={7}initialNumToRender={10} といった値を試すことができます。
  • この値を非常に大きく設定すると、パフォーマンス向上のメリットが失われ、初期ロード時間が長くなる可能性があります。
  • initialNumToRender は、リストの最初の表示にのみ影響します。ユーザーがスクロールすると、FlatListwindowSize などの他のプロパティに基づいてアイテムのレンダリングとアンマウントを動的に管理します。


FlatList#initialNumToRender のよくあるエラーとトラブルシューティング

initialNumToRender は、リストが最初にレンダリングされるアイテム数を制御することで、初期ロードのパフォーマンスを向上させるためのものです。しかし、このプロパティだけが FlatList のパフォーマンスを決定するわけではなく、他の多くの要因が絡み合っています。

空白領域の表示 (Blank Areas / White Screen)

問題: initialNumToRender の値が小さすぎる場合、ユーザーがリストを少しスクロールしただけで、まだコンテンツがロードされていない空白の領域(白い画面)が表示されてしまうことがあります。特に、デバイスの画面サイズが大きく、一度に多くのアイテムが表示される場合に顕著です。

原因: initialNumToRender で指定された数以上のアイテムが画面に表示されようとしているにも関わらず、まだレンダリングが追いついていないためです。

トラブルシューティング:

  • maxToRenderPerBatch の調整: これは initialNumToRender の後に、スクロール時に一度にレンダリングするアイテムのバッチ数を制御します。この値を増やすことで、スクロール時の空白を減らせますが、JavaScript スレッドの処理負荷が増え、応答性が低下する可能性があります。
  • windowSize の調整: FlatListwindowSize プロパティに基づいて、表示されているアイテムの上下にどれくらいの範囲のアイテムをレンダリングしておくかを決定します。デフォルトは21(表示領域の上下にそれぞれ10画面分ずつ、合計21画面分)ですが、これを増やすことで空白領域の発生を減らせます。ただし、メモリ使用量が増加する可能性があります。
  • initialNumToRender の値を増やす: 画面に表示されるアイテム数を十分にカバーできるような値に設定します。例えば、1画面に7つのアイテムが表示されるなら、initialNumToRender={10} 程度に設定すると良いでしょう。

初期ロードの遅延 (Slow Initial Render)

問題: initialNumToRender の値を大きくしすぎると、アプリケーションの起動時やリストが最初に表示されるときに、かえってロードに時間がかかり、UI が固まる(Jank)ことがあります。

原因: initialNumToRender は、初期表示時に同時にレンダリングされるアイテムの数を指定します。値が大きすぎると、その分多くのコンポーネントを一度にレンダリングしようとするため、JavaScript スレッドがブロックされ、UI がフリーズする原因となります。

トラブルシューティング:

  • renderItem コンポーネントの最適化: FlatList の各アイテム(renderItem でレンダリングされるコンポーネント)が重い処理を行っていないか確認します。
    • 複雑なロジックを避ける: renderItem 内で複雑な計算や多くの状態管理を行わないようにします。
    • 画像を最適化する: renderItem 内に表示される画像は、サイズを最適化したり、react-native-fast-image のようなキャッシュ対応ライブラリを使用したりすることを検討します。
    • React.memo / PureComponent の活用: renderItem でレンダリングされるアイテムコンポーネントを React.memo でラップしたり、クラスコンポーネントの場合は PureComponent を継承したりして、不要な再レンダリングを防ぎます。特に renderItem 関数自体を useCallback でメモ化することも重要です。
    • インライン関数の回避: renderItemkeyExtractor のプロパティに直接アロー関数を渡すのではなく、コンポーネントの外部で定義した関数を参照するようにします。これにより、不要な関数オブジェクトの再生成を防ぎます。
  • initialNumToRender の値を最適化する: 画面に収まる必要最小限のアイテム数に設定し、それ以上増やさないようにします。実際のデバイスでテストし、パフォーマンスモニタリングツール(Flipperなど)を使用して、最適な値を見つけることが重要です。

データが initialNumToRender 以降レンダリングされない (Data Not Loading Beyond Initial Num)

問題: initialNumToRender で指定した数までしかアイテムが表示されず、それ以降のアイテムがスクロールしても表示されない、または空白のままになることがあります。

原因: これは initialNumToRender 単体の問題というよりは、FlatList の仮想化機能やデータ管理に関する他の問題と複合して発生することが多いです。

  • InteractionManager が利用可能でない(例えば、バックグラウンドからの復帰時など)。
  • keyExtractor が正しく設定されていない。
  • data プロパティの更新が FlatList に認識されていない。

トラブルシューティング:

  • data プロパティの空配列問題: リストの初期状態が空の配列で、後からデータが追加される場合、初期レンダリングが期待通りにいかないことがあります。この場合は、initialNumToRender の値を調整するだけでなく、データのロードが完了してから FlatList をレンダリングするなどの工夫が必要になることもあります。
  • InteractionManager の理解: FlatListInteractionManager を使用して、アニメーションなどの優先度の高い UI 処理が終わった後に、画面外のアイテムのレンダリングをスケジュールすることがあります。もし InteractionManager が何らかの理由でブロックされていると、追加のアイテムがレンダリングされない可能性があります。ただし、これは通常、React Native 自体の問題であることが多いため、アプリケーションコードで直接操作することは稀です。
  • extraData プロパティの使用: FlatListPureComponent であるため、data プロパティが参照レベルで変更されない限り、再レンダリングされません。data の中身は変わっているが参照は変わっていない場合(例えば、配列内のオブジェクトの一部が変更されただけの場合)に、FlatList を強制的に再レンダリングさせるために extraData プロパティを使用します。
    const [selectedId, setSelectedId] = useState(null); // 例: 選択状態を管理
    <FlatList
      data={myData}
      renderItem={...}
      keyExtractor={...}
      extraData={selectedId} // 選択状態が変更されたらFlatListも再レンダリングする
    />
    
  • keyExtractor の確認: 各アイテムに一意の key が割り当てられていることを確認してください。keyExtractor プロパティが正しく実装されていないと、FlatList がアイテムを正しく追跡できず、レンダリングの問題を引き起こすことがあります。
    <FlatList
      data={myData}
      renderItem={...}
      keyExtractor={(item) => item.id.toString()} // 各アイテムに一意のIDがあることを確認
    />
    

スクロールパフォーマンスの低下 (Poor Scrolling Performance)

問題: リストのスクロールがカクついたり、スムーズでなかったりします。initialNumToRender の設定自体が直接的な原因であることは少ないですが、他の最適化が不足している場合に顕著になります。

原因: initialNumToRender は初期ロードに焦点を当てますが、スクロール中のパフォーマンスは FlatList の仮想化メカニズム全体に依存します。

  • 画像などのメディア要素が最適化されていない。
  • windowSize の値が不適切。
  • getItemLayout が利用されていない。
  • renderItem コンポーネントが重すぎる。

トラブルシューティング:

  • disableVirtualization={true} を避ける: このプロパティは仮想化を無効にし、すべてのアイテムを一度にレンダリングするため、リストのアイテム数が少ない場合にのみ使用すべきです。大量のデータを扱うリストでこれを使用すると、パフォーマンスが著しく低下し、メモリを大量に消費します。
  • decelerationRate="fast" の設定: スクロールの減速レートを調整することで、一部のデバイスでよりスムーズなスクロール体験を提供できる場合があります。
  • removeClippedSubviews={true} の使用: このプロパティを true に設定すると、画面外のビューをアンマウントし、メモリ使用量を削減できます。ただし、複雑な UI やアニメーションを使用している場合、予期せぬ動作を引き起こす可能性もあるため、注意が必要です。
  • renderItem の徹底的な最適化: 上記の「初期ロードの遅延」のトラブルシューティングで述べたように、renderItem でレンダリングされる各アイテムコンポーネントは可能な限り軽量であるべきです。
  • getItemLayout の実装: もしすべてのリストアイテムの高さ(または横方向リストの場合は幅)が固定されている場合、getItemLayout プロパティを実装することで、FlatList は各アイテムのサイズを非同期で計算する必要がなくなり、大幅なパフォーマンス向上に繋がります。これは最も効果的な最適化の一つです。
    const ITEM_HEIGHT = 100; // アイテムの固定の高さ
    <FlatList
      data={myData}
      renderItem={...}
      keyExtractor={...}
      getItemLayout={(data, index) => ({
        length: ITEM_HEIGHT,
        offset: ITEM_HEIGHT * index,
        index,
      })}
    />
    
  • 最新バージョンの React Native を使用する: React Native の開発チームは常に FlatList のパフォーマンス改善に取り組んでいます。可能な限り最新の安定版を使用することで、既存のバグ修正や最適化の恩恵を受けられます。
  • React DevTools (Flipper) でプロファイリング: Flipper の React Profiler を使用すると、どのコンポーネントが再レンダリングに時間を要しているか、レンダリングのボトルネックはどこにあるかなどを詳細に分析できます。これにより、問題の根本原因を特定しやすくなります。
  • console.log の使用を控える: 開発モードで console.log を大量に使用すると、JavaScript スレッドのパフォーマンスに悪影響を与えます。プロダクションビルドでは削除されるように設定するか、必要なデバッグ情報のみに限定してください。
  • 開発モードとリリースモードの違い: 開発モード (dev=true) では、デバッグ情報や警告の表示のために多くの処理がバックグラウンドで行われるため、パフォーマンスが低下します。必ずリリースモード (release ビルド) でパフォーマンスをテストしてください。


initialNumToRenderFlatList の初期表示パフォーマンスを最適化するためのプロパティです。ここでは、基本的な使い方から、関連するパフォーマンス最適化の例までをコードとともに解説します。

基本的な使い方

最もシンプルな FlatList の実装で initialNumToRender を設定する例です。

import React from 'react';
import { View, Text, FlatList, StyleSheet, Dimensions } from 'react-native';

// サンプルデータ
const DATA = Array.from({ length: 1000 }, (_, i) => ({
  id: String(i),
  title: `アイテム ${i + 1}`,
}));

// リストの各アイテムのコンポーネント
const Item = ({ title }) => (
  <View style={styles.item}>
    <Text style={styles.title}>{title}</Text>
  </View>
);

const BasicFlatListExample = () => {
  return (
    <View style={styles.container}>
      <FlatList
        data={DATA}
        renderItem={({ item }) => <Item title={item.title} />}
        keyExtractor={item => item.id}
        initialNumToRender={10} // ここで最初の10個のアイテムをレンダリングする
        ListHeaderComponent={<Text style={styles.header}>基本的なFlatListの例</Text>}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 50, // ステータスバーとの重なりを避けるため
  },
  item: {
    backgroundColor: '#f9c2ff',
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16,
    borderRadius: 8,
  },
  title: {
    fontSize: 20,
  },
  header: {
    fontSize: 24,
    fontWeight: 'bold',
    textAlign: 'center',
    marginVertical: 10,
  },
});

export default BasicFlatListExample;

解説:

  • これにより、大量のデータを持つリストでも、初期表示時のアプリケーションの起動時間を短縮し、よりスムーズなUI体験を提供できます。
  • initialNumToRender={10}: この設定により、FlatList は最初にマウントされたときに、DATA の中から最初の10個のアイテムだけをレンダリングします。残りのアイテムは、ユーザーがスクロールして表示領域に近づいたときに「遅延ロード」されます。

initialNumToRender と React.memo (または PureComponent) を組み合わせた例

initialNumToRender は初期ロードに役立ちますが、スクロール中のパフォーマンスをさらに向上させるには、各アイテムコンポーネントの不要な再レンダリングを防ぐことが重要です。React.memo を使用するのが一般的な方法です。

import React from 'react';
import { View, Text, FlatList, StyleSheet, Dimensions, TouchableOpacity } from 'react-native';

const DATA = Array.from({ length: 1000 }, (_, i) => ({
  id: String(i),
  title: `メモ化されたアイテム ${i + 1}`,
}));

// ItemコンポーネントをReact.memoでラップ
// propsが変更されない限り、再レンダリングされない
const MemoizedItem = React.memo(({ title, isSelected, onPress }) => {
  console.log(`Rendering Item: ${title}`); // レンダリング頻度を確認
  return (
    <TouchableOpacity onPress={onPress} style={[styles.item, isSelected && styles.selectedItem]}>
      <Text style={styles.title}>{title}</Text>
    </TouchableOpacity>
  );
});

const MemoizedFlatListExample = () => {
  const [selectedId, setSelectedId] = React.useState(null);

  // renderItem関数もuseCallbackでメモ化するとさらに良い
  // FlatListが再レンダリングされる際に、この関数が再生成されないようにする
  const renderItem = React.useCallback(({ item }) => (
    <MemoizedItem
      title={item.title}
      isSelected={item.id === selectedId}
      onPress={() => setSelectedId(item.id)}
    />
  ), [selectedId]); // selectedIdが変更されたときのみ再生成

  return (
    <View style={styles.container}>
      <FlatList
        data={DATA}
        renderItem={renderItem}
        keyExtractor={item => item.id}
        initialNumToRender={10} // 初期表示のアイテム数
        windowSize={21} // 表示されている領域の上下にどれくらいアイテムをレンダリングしておくか(デフォルト値)
        maxToRenderPerBatch={5} // initialNumToRender以降、スクロール時に一度にレンダリングするアイテムのバッチ数
        // extraDataを渡すことで、selectedIdが変更されたときにFlatList全体を再レンダリングさせる
        // これがないと、MemoizedItemはisSelectedの変更を検知できない
        extraData={selectedId}
        ListHeaderComponent={<Text style={styles.header}>Memoized ItemとFlatListの例</Text>}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 50,
  },
  item: {
    backgroundColor: '#e0f7fa',
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16,
    borderRadius: 8,
  },
  selectedItem: {
    backgroundColor: '#00bcd4',
  },
  title: {
    fontSize: 20,
  },
  header: {
    fontSize: 24,
    fontWeight: 'bold',
    textAlign: 'center',
    marginVertical: 10,
  },
});

export default MemoizedFlatListExample;

解説:

  • extraData={selectedId}: FlatList は内部的に PureComponent のような挙動をするため、data プロパティの参照が変わらない限り、FlatList は再レンダリングされません。しかし、この例では selectedId が変更されると、一部のアイテムの isSelected プロパティが変更される必要があります。extraDataselectedId を渡すことで、selectedId が変更されたときに FlatList に再レンダリングを促し、関連する MemoizedItem が適切に更新されるようにします。
  • renderItem = React.useCallback(...): renderItem 関数自体を useCallback でメモ化しています。これにより、MemoizedFlatListExample コンポーネントが再レンダリングされても、renderItem 関数が再生成されることを防ぎ、FlatList の不要な再レンダリングを避けることができます。
  • MemoizedItem = React.memo(...): 各アイテムコンポーネントを React.memo でラップすることで、MemoizedItem に渡される props が変更されない限り、そのアイテムは再レンダリングされません。これにより、スクロール時の不要な再レンダリングが大幅に減り、パフォーマンスが向上します。console.log を見ると、選択されたアイテムとその周辺のアイテムのみが再レンダリングされていることがわかります。

initialNumToRender と getItemLayout を組み合わせた例 (固定高さのアイテム)

リスト内のすべてのアイテムの高さが固定されている場合、getItemLayout プロパティを実装することで、FlatList のスクロールパフォーマンスを劇的に向上させることができます。これは initialNumToRender の設定とは独立して重要です。

import React from 'react';
import { View, Text, FlatList, StyleSheet, Dimensions } from 'react-native';

const DATA = Array.from({ length: 1000 }, (_, i) => ({
  id: String(i),
  title: `固定高さのアイテム ${i + 1}`,
}));

const ITEM_HEIGHT = 80; // 各アイテムの固定の高さ

const FixedHeightItem = ({ title }) => (
  <View style={styles.fixedItem}>
    <Text style={styles.title}>{title}</Text>
  </View>
);

const GetItemLayoutFlatListExample = () => {
  return (
    <View style={styles.container}>
      <FlatList
        data={DATA}
        renderItem={({ item }) => <FixedHeightItem title={item.title} />}
        keyExtractor={item => item.id}
        initialNumToRender={10} // 初期表示のアイテム数
        // ここが重要:getItemLayoutの実装
        getItemLayout={(data, index) => (
          { length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index }
        )}
        ListHeaderComponent={<Text style={styles.header}>getItemLayoutとFlatListの例</Text>}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 50,
  },
  fixedItem: {
    backgroundColor: '#c8e6c9',
    height: ITEM_HEIGHT, // 高さを固定
    justifyContent: 'center',
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16,
    borderRadius: 8,
  },
  title: {
    fontSize: 20,
  },
  header: {
    fontSize: 24,
    fontWeight: 'bold',
    textAlign: 'center',
    marginVertical: 10,
  },
});

export default GetItemLayoutFlatListExample;

解説:

  • 注意: アイテムの高さが動的に変わる場合、getItemLayout は使用できません。その場合は、React.memowindowSize などの他の最適化に頼ることになります。
  • getItemLayout を実装すると、FlatList は各アイテムのサイズと位置を事前に計算できるようになります。これにより、スクロール時のレイアウト計算のオーバーヘッドがなくなり、非常にスムーズなスクロールが可能になります。
  • getItemLayout={(data, index) => ({ length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index })}:
    • length: 各アイテムの長さを指定します(この場合は高さ)。
    • offset: リストの先頭からのアイテムのオフセット(位置)を指定します。
    • index: アイテムのインデックスです。
  • ITEM_HEIGHT = 80: 各アイテムの固定の高さを定義します。

FlatList#initialNumToRender は、リストの初期ロードパフォーマンスを改善する素晴らしいツールですが、最高のパフォーマンスを得るためには、以下の点を考慮してコードを最適化することが重要です。

  1. initialNumToRender の適切な値: 画面に表示されるアイテム数より少し多めに設定し、空白表示を避けつつ、初期ロードの遅延を防ぎます。
  2. renderItem コンポーネントの最適化:
    • 軽量化: アイテムコンポーネント内のロジックや描画をできるだけシンプルにします。
    • React.memo の使用: props が変更されない限り再レンダリングしないように、renderItem でレンダリングされるコンポーネントを React.memo でラップします。
    • useCallback の使用: renderItem 関数自体を useCallback でメモ化し、親コンポーネントの再レンダリングによる不要な関数再生成を防ぎます。
  3. keyExtractor の適切な実装: 各アイテムに一意のキーを割り当て、FlatList がアイテムを効率的に追跡できるようにします。
  4. getItemLayout の活用: アイテムの高さが固定されている場合は、必ず getItemLayout を実装します。これはパフォーマンスに最も大きな影響を与える最適化の一つです。
  5. extraData の使用: data プロパティの参照が変わらないが、アイテム内部のデータが変更された場合に、FlatList に再レンダリングを促します。
  6. windowSizemaxToRenderPerBatch の調整: 必要に応じてこれらのプロパティを調整し、スクロール中の空白領域とメモリ使用量のバランスを取ります。


initialNumToRenderFlatList が最初に描画するアイテム数を制御しますが、リストのパフォーマンス全体は、初期レンダリングだけでなく、スクロール中のパフォーマンス、メモリ使用量、データ取得方法など、様々な要因によって決まります。ここでは、initialNumToRender と組み合わせて使用することで効果を高める方法や、異なるアプローチを紹介します。

windowSize の調整

windowSizeFlatList が現在の表示領域(viewport)の上下にどれくらいのアイテムをレンダリングしておくかを決定するプロパティです。これは initialNumToRender と密接に関連しており、特にスクロール中のパフォーマンスに影響します。

  • 調整の目的: windowSize を増やすと、ユーザーが素早くスクロールしたときに空白領域が表示されるのを防ぐことができますが、同時にメモリ使用量が増加し、レンダリング負荷も増えます。逆に減らすとメモリ使用量は減りますが、空白領域が表示されやすくなります。
  • デフォルト値: デフォルトは 21 です(ビューポートの上下にそれぞれ10画面分ずつ、合計21画面分)。

コード例: initialNumToRender と同様に FlatList のプロパティとして設定します。

<FlatList
  data={myData}
  renderItem={({ item }) => <MyListItem item={item} />}
  keyExtractor={item => item.id}
  initialNumToRender={10} // 例: 初期は10個
  windowSize={11} // 例: 画面上下に5画面分ずつ(合計11画面分)
/>

解説: initialNumToRender は「最初の表示」に特化していますが、windowSize は「スクロール中のレンダリングバッファ」と考えると良いでしょう。両方を適切に設定することで、初期ロードとスクロールパフォーマンスの両方を最適化できます。

maxToRenderPerBatch の調整

これは initialNumToRender の後に、スクロール時に一度にレンダリングするアイテムのバッチ数を制御するプロパティです。

  • 注意点: 値を大きくしすぎると、一度に多くの処理が走り、JavaScript スレッドがブロックされ、UI がカクつく可能性があります。
  • 目的: スクロール中に一度に多くのアイテムをレンダリングすることで、空白領域の表示を減らすことができます。

コード例:

<FlatList
  data={myData}
  renderItem={({ item }) => <MyListItem item={item} />}
  keyExtractor={item => item.id}
  initialNumToRender={10}
  maxToRenderPerBatch={5} // デフォルトは10ですが、必要に応じて調整
/>

解説: maxToRenderPerBatch は、initialNumToRender で表示された後、ユーザーがスクロールするたびに、追加でどれだけのアイテムをレンダリングするかを制御します。これもまた、空白領域の回避とパフォーマンスのバランスを取るための重要なプロパティです。

getItemLayout の活用 (最も効果的な最適化の一つ)

もしリスト内のすべてのアイテムの高さ(または幅)が固定されている場合、getItemLayout を実装することは、FlatList のスクロールパフォーマンスを劇的に向上させる最も効果的な方法です。これは initialNumToRender の代替というよりは、強力な補完です。

  • なぜ効果的か: FlatList は各アイテムのサイズとオフセットを非同期で計算しますが、getItemLayout を提供することで、この計算をスキップし、正確な位置を瞬時に決定できるようになります。これにより、仮想化が非常に効率的に機能し、スクロールが非常にスムーズになります。

コード例:

const ITEM_HEIGHT = 100; // 各アイテムの固定の高さ

<FlatList
  data={myData}
  renderItem={({ item }) => <MyListItem item={item} />}
  keyExtractor={item => item.id}
  initialNumToRender={10}
  getItemLayout={(data, index) => (
    { length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index }
  )}
/>

解説: initialNumToRender が初期ロードを助けるのに対し、getItemLayout はリスト全体のレンダリングとスクロールの効率性を高めます。アイテムの高さが固定されている場合は、必ずこのプロパティを使用することを強くお勧めします。

renderItem コンポーネントの徹底的な最適化

initialNumToRender や他のプロパティ設定も重要ですが、個々のリストアイテム(renderItem で描画されるコンポーネント)が重いと、どんな最適化も効果が薄れてしまいます。これは initialNumToRender の直接的な代替ではありませんが、パフォーマンス改善の最も重要な基礎です。

  • インライン関数の回避:
    • renderItemkeyExtractor のプロパティに直接アロー関数を渡すのではなく、コンポーネントの外部で定義した関数を参照するようにします。
  • 軽量なアイテムコンポーネント:
    • アイテム内で複雑な計算や多くの状態管理を行わない。
    • 画像の最適化(サイズ、フォーマット、react-native-fast-image のようなライブラリの使用)。
    • 可能な限りシンプルなビュー構造にする。
  • useCallbackrenderItem をメモ化:
    • 親コンポーネントが再レンダリングされる際に、renderItem 関数が再生成されることを防ぎ、FlatList の不要な再レンダリングを避けます。
  • React.memo (または PureComponent) の使用:
    • アイテムコンポーネントを React.memo でラップすることで、props が変更されない限り不要な再レンダリングを防ぎます。

コード例: (上記「プログラミング例」の2.で詳細に解説済み)

// アイテムコンポーネントをメモ化
const MemoizedItem = React.memo(({ title, isSelected, onPress }) => { /* ... */ });

// renderItem関数もメモ化
const renderItem = React.useCallback(({ item }) => (
  <MemoizedItem
    title={item.title}
    isSelected={item.id === selectedId}
    onPress={() => setSelectedId(item.id)}
  />
), [selectedId]); // 依存配列に注意

<FlatList
  data={DATA}
  renderItem={renderItem} // メモ化した関数を参照
  keyExtractor={item => item.id}
  initialNumToRender={10}
  extraData={selectedId} // 関連するstateを伝える
/>

removeClippedSubviews の使用

このプロパティを true に設定すると、画面外のビューをアンマウントし、メモリ使用量を削減することができます。

  • 注意点: 複雑な UI やアニメーションを使用している場合、予期せぬ動作や視覚的な不具合を引き起こす可能性があるため、慎重に使用し、十分にテストする必要があります。
  • 目的: 特に多数のアイテムを持つリストでメモリ使用量を抑えたい場合に有効です。

コード例:

<FlatList
  data={myData}
  renderItem={({ item }) => <MyListItem item={item} />}
  keyExtractor={item => item.id}
  initialNumToRender={10}
  removeClippedSubviews={true} // 画面外のビューをアンマウントする
/>

仮想化が不要な場合の代替 (ScrollView)

もしリストのアイテム数が非常に少なく(例えば20個以下)、かつすべてのアイテムが一度に画面に表示されるような場合、あるいは仮想化による問題(例えば複雑なヘッダーやフッターのレイアウト)を避けたい場合は、FlatList の代わりにシンプルな ScrollView を使用することも検討できます。

  • ScrollView の欠点:
    • 大量のデータには不向き。すべてのアイテムが一度にレンダリングされるため、メモリを大量に消費し、パフォーマンスが著しく低下する。
  • ScrollView の利点:
    • セットアップが簡単。
    • 仮想化の概念がないため、initialNumToRenderwindowSize のようなプロパティを気にする必要がない。
    • すべてのコンテンツが一度にレンダリングされるため、動的な高さのコンテンツや複雑なネストされたビューとの相性が良い場合がある。

コード例:

import React from 'react';
import { View, Text, ScrollView, StyleSheet } from 'react-native';

const DATA = Array.from({ length: 15 }, (_, i) => ({ // 少ないデータ
  id: String(i),
  title: `スクロールビューアイテム ${i + 1}`,
}));

const Item = ({ title }) => (
  <View style={styles.item}>
    <Text style={styles.title}>{title}</Text>
  </View>
);

const ScrollViewExample = () => {
  return (
    <ScrollView style={styles.container}>
      <Text style={styles.header}>ScrollViewの例</Text>
      {DATA.map(item => (
        <Item key={item.id} title={item.title} />
      ))}
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 50,
  },
  item: {
    backgroundColor: '#ffccbc',
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16,
    borderRadius: 8,
  },
  title: {
    fontSize: 20,
  },
  header: {
    fontSize: 24,
    fontWeight: 'bold',
    textAlign: 'center',
    marginVertical: 10,
  },
});

export default ScrollViewExample;

解説: ScrollView は、アイテム数が少ない場合に、FlatList の仮想化による複雑さを避けたい場合の代替となりえます。ただし、パフォーマンス要件が厳しい場合やデータ量が多い場合は、FlatList の使用と上記の最適化が必須となります。

FlatList#initialNumToRender は初期ロードの最適化に役立ちますが、リスト全体のパフォーマンスを最大化するためには、以下の代替・補完的な手法を総合的に考慮することが重要です。

  • ScrollView: 少ないアイテム数の場合のシンプルな代替手段。
  • removeClippedSubviews: メモリ使用量の削減。
  • renderItem コンポーネントの徹底的な最適化: React.memouseCallback を活用し、各アイテムのレンダリングコストを最小限に抑える。
  • getItemLayout: 固定高さのアイテムにおける最も効果的なスクロールパフォーマンス向上策。
  • windowSizemaxToRenderPerBatch: スクロール中のバッファリングとバッチレンダリングの制御。