React Native FlatListパフォーマンス向上ガイド:getItemLayoutから代替案まで

2025-05-31

getItemLayoutとは何か?

FlatListは、非常に長いリストを効率的に表示するために「仮想化 (Virtualization)」という技術を使用しています。これは、画面に表示されているアイテムのみをレンダリングし、スクロールに応じて不要なアイテムをアンマウントし、新しいアイテムをレンダリングすることでメモリ使用量とパフォーマンスを最適化する仕組みです。

通常、FlatListは各アイテムのサイズ(高さまたは幅)を測定することで、リスト全体のレイアウトを計算します。しかし、この測定は非同期で行われるため、特にアイテム数が多い場合や、アイテムのサイズが動的に変わる場合に、スクロールがカクついたり、空白が表示されたりするなどのパフォーマンスの問題が発生することがあります。

getItemLayoutプロパティは、この問題に対する最適化策です。このプロパティに関数を渡すことで、FlatList各アイテムのサイズと位置を事前に教えることができます。これにより、FlatListはアイテムの測定をスキップし、事前に計算された情報に基づいてスムーズにスクロールできるようになります。

getItemLayoutのメリット

  • 正確なスクロール位置の維持: scrollToIndexなどのメソッドを使用する際に、より正確なスクロール位置を保証できます。
  • スムーズなスクロール: アイテムのレイアウト計算によるカクつきが軽減され、よりスムーズなスクロール体験を提供します。
  • パフォーマンスの向上: アイテムの測定が不要になるため、特に長いリストでの初回レンダリングやスクロール時のパフォーマンスが大幅に向上します。

getItemLayout関数のシグネチャと戻り値

getItemLayoutに渡す関数は、以下のシグネチャを持ちます。

getItemLayout={(data, index) => ({
  length: number, // 各アイテムの長さ(高さまたは幅)
  offset: number, // そのアイテムがリストの先頭からどれだけ離れているか
  index: number,  // アイテムのインデックス
})}
  • index: 現在処理されているアイテムのインデックスです。
  • data: FlatListに渡されたdataプロパティ(リストのデータ配列)です。

戻り値は以下のプロパティを持つオブジェクトです。

  • index: そのアイテムのインデックスです。
  • offset: そのアイテムがリストの先頭からどれだけ離れているか(ピクセル単位)を指定します。これは通常、length * indexで計算されます。アイテム間に区切り線などがある場合は、その長さも考慮する必要があります。
  • length: そのアイテムの長さ(垂直方向のリストなら高さ、水平方向のリストなら幅)をピクセル単位で指定します。

getItemLayoutを使用する際の注意点

  • 不正確な値の指定: もしgetItemLayoutで不正確な値を返してしまうと、スクロール位置のずれやアイテムの表示がおかしくなるなどの問題が発生する可能性があります。
  • 横方向のリストの場合: horizontalプロパティをtrueにしている場合、lengthはアイテムの「幅」を、offsetはリストの「左端」からの距離を示します。
  • セパレータの考慮: ItemSeparatorComponentなどでアイテム間にスペースや区切り線がある場合、offsetの計算にその分も考慮する必要があります。
    • 例: offset: (ITEM_HEIGHT + SEPARATOR_HEIGHT) * index
  • アイテムのサイズが固定であること: getItemLayoutが最も効果を発揮するのは、すべてのリストアイテムのサイズが固定である場合です。アイテムのサイズが動的に変わる場合は、正確なlengthoffsetを事前に計算することが難しく、かえって問題を引き起こす可能性があります。
    • もしアイテムの高さが異なる場合でも、各アイテムの正確な高さを事前に計算できるのであれば使用可能です。例えば、ヘッダーの高さが固定で、その下のアイテムの高さも固定といった場合です。

例えば、すべてのアイテムの高さが50ピクセルである垂直方向のFlatListの場合、getItemLayoutは以下のように実装できます。

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

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

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

  return (
    <FlatList
      data={DATA}
      renderItem={renderItem}
      keyExtractor={(item) => item.id}
      getItemLayout={(data, index) => (
        {
          length: ITEM_HEIGHT, // 各アイテムの高さ
          offset: ITEM_HEIGHT * index, // 先頭からのオフセット
          index,
        }
      )}
    />
  );
};

const styles = StyleSheet.create({
  item: {
    height: ITEM_HEIGHT,
    backgroundColor: '#f9c2ff',
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16,
    justifyContent: 'center',
    alignItems: 'center',
  },
  title: {
    fontSize: 16,
  },
});

export default MyFlatList;


アイテムの高さ/幅が不正確な場合 (Offset/Length Calculation Mismatch)

これは最も一般的なエラーです。getItemLayoutで返されるlength(高さまたは幅)やoffsetが、実際のアイテムのレンダリングサイズと一致しない場合に発生します。

症状:

  • 一部のアイテムが表示されない(特にリストの下部)。
  • scrollToIndexscrollToEndなどのスクロールメソッドが、意図した位置にスクロールしない。
  • リストをスクロールすると、アイテム間に不自然な空白ができる、またはアイテムが重なって表示される。

原因:

  • numColumnsを使用している場合、各行のオフセット計算が複雑になる。
  • horizontalプロパティがtrueなのに、lengthに高さを指定している(幅を指定すべき)。
  • アイテム間にmarginpaddingborderなどのスペースがあるにもかかわらず、offsetの計算にそれらが含まれていない。
  • getItemLayoutで指定したlengthが、実際のrenderItemで描画されるアイテムの高さ(または幅)と異なる。

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

  • numColumnsの考慮:
    • numColumnsを使用する場合、offsetの計算がより複雑になります。各アイテムの幅を考慮し、行の高さと列のインデックスに基づいて計算する必要があります。一般的には、numColumnsを使用する場合はgetItemLayoutの恩恵を受けにくいか、より高度な計算が必要になります。可能であれば、numColumnsgetItemLayoutの併用は避けるか、慎重に実装を検討してください。
  • horizontalプロパティを確認する:
    • horizontal={true}の場合は、lengthをアイテムの幅、offsetwidth * indexで計算します。
  • デバッグ用のボーダーを追加する:
    • renderItemで描画される各アイテムに一時的に目立つボーダー(例: borderWidth: 1, borderColor: 'red')を設定し、実際にどのくらいのスペースを占めているかを目視で確認します。
  • マージン・パディングを考慮する:
    • アイテム間隔もlengthに含めるか、offset計算に加えます。
    • 例: length: ITEM_HEIGHT + ITEM_MARGIN_VERTICAL
    • 例: offset: (ITEM_HEIGHT + ITEM_MARGIN_VERTICAL) * index
  • 正確なアイテムサイズを把握する:
    • renderItemで描画されるコンポーネントに固定の高さを与え、その値をgetItemLayoutlengthに設定します。
    • StyleSheet.createで定義したスタイルから高さを取得するか、コンポーネントのプロパティとして渡すなどして、一貫した値を確保します。

アイテムのサイズが動的に変わる場合

getItemLayoutは、アイテムのサイズが事前にわかる場合に最適化効果を発揮します。アイテムの高さや幅がデータによって動的に変わる場合、getItemLayoutは適切ではありません。

症状:

  • コンテンツが動的に変化したときに、リストのレイアウトが正しく更新されない。
  • 上記の「高さ/幅の不正確」と同じような表示の乱れが発生する。

原因:

  • getItemLayoutで固定値を返しているが、実際のアイテムのコンテンツ(例: テキストの行数、画像のロードなど)によって高さが変わる。

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

  • FlashListの検討:
    • Shopifyが提供するFlashListは、動的な高さのアイテムに対するパフォーマンスをより良く処理できるように設計されています。もしパフォーマンスが最優先で、動的な高さが避けられない場合は、FlashListへの移行を検討する価値があります。
  • onLayoutで動的な高さを測定する:
    • どうしてもgetItemLayoutを使いたいが、一部のアイテムの高さが動的な場合は、各アイテム内でonLayoutイベントを使用して実際の高さを測定し、それを親コンポーネントに伝える方法も考えられます。ただし、これは非常に複雑になり、パフォーマンスメリットを打ち消す可能性があります。通常は推奨されません。
  • getItemLayoutを使用しない:
    • 動的な高さのアイテムがある場合、getItemLayoutの使用は避けるべきです。FlatListは、アイテムがレンダリングされた後に自動的にそのサイズを測定します。パフォーマンスが問題になる場合は、他の最適化手法(windowSizeinitialNumToRendermaxToRenderPerBatchremoveClippedSubviewsなど)を検討してください。

getItemLayoutが呼ばれない、または効果がないように見える場合

症状:

  • console.logなどでgetItemLayout内の処理が呼ばれていないように見える。
  • getItemLayoutを実装したのに、スクロールパフォーマンスに変化が見られない。

原因:

  • 不適切なkeyExtractorなど、他のFlatListのプロパティ設定が影響している可能性。
  • デバッグモード(リモートデバッグ)を使用している場合、パフォーマンスが低下するため、getItemLayoutの効果が分かりにくいことがある。
  • FlatListgetItemLayoutの情報を利用する前に、すでにすべてのアイテムがレンダリングされている(例: リストが非常に短い場合)。

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

  • 他の最適化プロパティを確認する:
    • keyExtractorが適切に設定されているか確認します。一意のキーが設定されていないと、FlatListはアイテムの再レンダリングや追跡を正しく行えません。
    • initialNumToRenderwindowSizemaxToRenderPerBatchなどのプロパティも、getItemLayoutの効果を補完したり、時には妨げたりする可能性があります。これらの値を調整してみることも検討します。
  • 本番ビルドで試す:
    • デバッグモードではなく、リリースビルド(またはHermesエンジンを有効にする)でパフォーマンスを評価します。デバッグモードではJavaScriptスレッドのパフォーマンスが低下するため、最適化の効果が隠れてしまうことがあります。
  • リストのアイテム数を増やす:
    • getItemLayoutの真価は、数百または数千のアイテムがあるような長いリストで発揮されます。短いリストでは、その効果は体感しにくいかもしれません。

ごく稀に、getItemLayout関数にindex-1として渡されるという報告があります。これは、FlatList内部の特殊なケースやバグが原因である可能性があります。

症状:

  • getItemLayout内でindex-1になることで、配列の参照エラーなどが発生する。

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

  • index-1の場合のガード処理を追加する:
    • 確実な解決策ではないかもしれませんが、一時的な回避策として、index-1の場合は適切なデフォルト値(例: length: 0, offset: 0, index)を返すように処理を追加します。
getItemLayout={(data, index) => {
  if (index === -1) {
    // 例外的なケースへの対応
    return { length: 0, offset: 0, index };
  }
  return {
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  };
}}


getItemLayoutは、リストアイテムの高さ(または幅)が事前にわかっている場合に、FlatListのスクロールパフォーマンスを大幅に向上させるために使用されます。

基本例:固定の高さのアイテム

最も一般的なケースで、すべてのリストアイテムが同じ固定の高さを持ちます。

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

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

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

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

  return (
    <FlatList
      data={DATA}
      renderItem={renderItem}
      keyExtractor={(item) => item.id}
      // ここが重要:getItemLayoutの実装
      getItemLayout={(data, index) => ({
        length: ITEM_HEIGHT, // アイテムの高さ
        offset: ITEM_HEIGHT * index, // 先頭からのオフセット
        index, // アイテムのインデックス
      })}
      initialNumToRender={10} // 初期表示数を設定(パフォーマンス最適化)
      windowSize={21} // レンダリングされるビューポート外のアイテム数を設定
      maxToRenderPerBatch={10} // 各バッチでレンダリングするアイテム数
    />
  );
};

const styles = StyleSheet.create({
  item: {
    height: ITEM_HEIGHT, // getItemLayoutで指定した高さと合わせる
    backgroundColor: '#f9c2ff',
    padding: 20,
    marginVertical: 8, // マージンも考慮する必要がある場合は、ITEM_HEIGHTに含める
    marginHorizontal: 16,
    justifyContent: 'center',
    alignItems: 'flex-start',
    borderRadius: 8,
  },
  title: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#333',
  },
});

export default FixedHeightList;

ポイント:

  • offsetITEM_HEIGHT * indexで計算されます。これは、そのアイテムがリストの先頭からどれだけ離れているかを示します。
  • ITEM_HEIGHTという定数でアイテムの高さを定義し、それをFlatListのスタイルとgetItemLayoutの両方で使用しています。これにより、値の一貫性が保たれます。

例2:固定の高さ + アイテム間のマージン/セパレータ

アイテム間にマージンやセパレータ(区切り線)がある場合、その分もoffsetの計算に含める必要があります。

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

const DATA = Array.from({ length: 500 }, (_, i) => ({
  id: String(i),
  title: `アイテム ${i}`,
}));

const ITEM_BASE_HEIGHT = 60; // アイテム自体の高さ
const ITEM_MARGIN_VERTICAL = 10; // アイテムの上下マージン
const TOTAL_ITEM_HEIGHT = ITEM_BASE_HEIGHT + ITEM_MARGIN_VERTICAL; // マージンを含めた全体の高さ

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

  return (
    <SafeAreaView style={{ flex: 1 }}>
      <FlatList
        data={DATA}
        renderItem={renderItem}
        keyExtractor={(item) => item.id}
        getItemLayout={(data, index) => ({
          length: TOTAL_ITEM_HEIGHT, // マージンを含めたアイテムの全長
          offset: TOTAL_ITEM_HEIGHT * index, // その合計長を基準にオフセットを計算
          index,
        })}
      />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  item: {
    height: ITEM_BASE_HEIGHT, // ここはアイテム自体の高さ
    backgroundColor: '#add8e6',
    padding: 15,
    marginVertical: ITEM_MARGIN_VERTICAL / 2, // 上下のマージンを半分ずつ設定することで、アイテム間にTOTAL_ITEM_HEIGHTのマージンがあるように見せる
    marginHorizontal: 16,
    justifyContent: 'center',
    borderRadius: 5,
  },
  title: {
    fontSize: 16,
    color: '#333',
  },
});

export default FixedHeightWithMarginList;

ポイント:

  • marginVerticalは、TOTAL_ITEM_HEIGHTがアイテム間のスペースを考慮するように、ITEM_MARGIN_VERTICAL / 2として設定しています。これにより、各アイテムの上と下にITEM_MARGIN_VERTICAL / 2のマージンがつき、結果的にアイテムとアイテムの間にはITEM_MARGIN_VERTICALのマージンが生まれます。
  • getItemLayoutlengthoffsetには、このTOTAL_ITEM_HEIGHTを使用します。
  • ITEM_BASE_HEIGHTITEM_MARGIN_VERTICALを定義し、TOTAL_ITEM_HEIGHTでアイテムの「占めるべき全体のスペース」を計算しています。

例3:ヘッダー/フッターがあり、アイテムの高さが固定の場合

リストに固定の高さのヘッダーやフッターがある場合も、getItemLayoutを適用できます。この場合、ヘッダー/フッターの高さもオフセット計算に含める必要があります。

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

const DATA = Array.from({ length: 500 }, (_, i) => ({
  id: String(i),
  title: `アイテム ${i}`,
}));

const ITEM_HEIGHT = 70;
const HEADER_HEIGHT = 100;
const FOOTER_HEIGHT = 80;

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

  const renderHeader = () => (
    <View style={styles.header}>
      <Text style={styles.headerText}>これはヘッダーです</Text>
    </View>
  );

  const renderFooter = () => (
    <View style={styles.footer}>
      <Text style={styles.footerText}>これはフッターです</Text>
    </View>
  );

  return (
    <SafeAreaView style={{ flex: 1 }}>
      <FlatList
        data={DATA}
        renderItem={renderItem}
        keyExtractor={(item) => item.id}
        ListHeaderComponent={renderHeader}
        ListFooterComponent={renderFooter}
        getItemLayout={(data, index) => {
          let offset = HEADER_HEIGHT; // ヘッダーの高さでオフセットを初期化
          offset += index * ITEM_HEIGHT; // アイテム分のオフセットを追加

          return {
            length: ITEM_HEIGHT,
            offset: offset,
            index,
          };
        }}
      />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  header: {
    height: HEADER_HEIGHT, // getItemLayoutで指定した高さと合わせる
    backgroundColor: '#ffd700',
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  headerText: {
    fontSize: 22,
    fontWeight: 'bold',
  },
  item: {
    height: ITEM_HEIGHT, // getItemLayoutで指定した高さと合わせる
    backgroundColor: '#b0e0e6',
    padding: 20,
    marginVertical: 4,
    marginHorizontal: 16,
    justifyContent: 'center',
    alignItems: 'flex-start',
    borderRadius: 4,
  },
  title: {
    fontSize: 16,
    color: '#333',
  },
  footer: {
    height: FOOTER_HEIGHT, // getItemLayoutではフッターの考慮は不要(リストの最後に自動的に配置されるため)
    backgroundColor: '#98fb98',
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  footerText: {
    fontSize: 18,
    fontWeight: 'bold',
  },
});

export default HeaderFooterList;

ポイント:

  • フッターの高さはgetItemLayoutの計算には直接影響しません。FlatListはフッターをリストの最後に自動的に配置するためです。
  • getItemLayoutoffset計算で、まずHEADER_HEIGHT分を追加しています。これにより、すべてのアイテムがヘッダーの下から開始されるようにオフセットが調整されます。

horizontalプロパティをtrueに設定した場合、getItemLayoutlengthはアイテムの「幅」を、offsetはリストの「左端」からの距離を示します。

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

const DATA = Array.from({ length: 50 }, (_, i) => ({
  id: String(i),
  title: `横アイテム ${i}`,
}));

const ITEM_WIDTH = Dimensions.get('window').width * 0.7; // 画面幅の70%をアイテムの幅とする
const ITEM_MARGIN_HORIZONTAL = 10;
const TOTAL_ITEM_WIDTH = ITEM_WIDTH + ITEM_MARGIN_HORIZONTAL;

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

  return (
    <SafeAreaView style={styles.container}>
      <Text style={styles.listTitle}>水平スクロールリスト</Text>
      <FlatList
        data={DATA}
        renderItem={renderItem}
        keyExtractor={(item) => item.id}
        horizontal // 水平方向スクロールを有効にする
        showsHorizontalScrollIndicator={false} // スクロールインジケーターを非表示
        getItemLayout={(data, index) => ({
          length: TOTAL_ITEM_WIDTH, // アイテムの幅(マージン含む)
          offset: TOTAL_ITEM_WIDTH * index, // 左端からのオフセット
          index,
        })}
      />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: 20,
  },
  listTitle: {
    fontSize: 20,
    fontWeight: 'bold',
    marginBottom: 10,
    marginLeft: 16,
  },
  item: {
    width: ITEM_WIDTH, // getItemLayoutで指定した幅と合わせる
    height: 150, // 高さ
    backgroundColor: '#87ceeb',
    padding: 15,
    marginHorizontal: ITEM_MARGIN_HORIZONTAL / 2, // 左右のマージンを半分ずつ
    justifyContent: 'center',
    alignItems: 'center',
    borderRadius: 10,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.2,
    shadowRadius: 3,
    elevation: 5,
  },
  title: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#fff',
    textAlign: 'center',
  },
});

export default HorizontalList;

ポイント:

  • スタイルシートのitemでもwidthプロパティを設定し、getItemLayoutと一致させる必要があります。
  • getItemLayoutlengthにはアイテムのを、offsetにはwidth * indexを使用します。
  • horizontalプロパティをtrueに設定しています。

これらの例は、getItemLayoutをさまざまなシナリオでどのように使用するかを示しています。最も重要なことは、getItemLayoutに渡すlengthoffsetの値が、実際に画面にレンダリングされるアイテムのサイズと位置に正確に一致していることを確認することです。これにより、FlatListはアイテムの測定処理をスキップし、スムーズで高性能なスクロールを実現できます。



React NativeのFlatListにおけるgetItemLayoutは、リストのアイテムが固定のサイズを持つ場合に最高のパフォーマンスを発揮する最適化手法です。しかし、すべてのリストでアイテムのサイズが固定とは限りません。アイテムの高さや幅が動的に変わる場合、getItemLayoutは使用できません(または、使用すると問題が発生します)。

ここでは、getItemLayoutが使えない、または適さない場合の代替手段と、FlatListの他の最適化手法について説明します。

getItemLayout以外のFlatListの最適化プロパティ

getItemLayoutを使用できない場合でも、FlatListには他にもパフォーマンスを向上させるためのプロパティがいくつかあります。これらは、getItemLayoutと組み合わせて使うこともできます。

  • renderItemの最適化:

    • 説明: renderItem関数内で直接アロー関数を使用するのを避け、コンポーネントの外で定義するか、useCallbackでメモ化することを推奨します。これにより、FlatListが再レンダリングされるたびに関数が再作成されるのを防ぎます。
    • アイテムコンポーネントの軽量化: 各リストアイテムのコンポーネントは、できるだけシンプルで、ネストが少なく、計算コストの低いものであるべきです。複雑なロジックや重い画像処理は、アイテムの詳細画面に移動させるなどして、リストアイテム自体の負担を減らします。
    • React.memo (または PureComponent) の使用: renderItemで描画されるアイテムコンポーネントが複雑で、propsが頻繁に変わらない場合は、React.memo (関数コンポーネントの場合) やPureComponent (クラスコンポーネントの場合) でメモ化することで、不要な再レンダリングを防ぐことができます。ただし、propsが複雑だったり、頻繁に変わる場合は、かえってパフォーマンスが低下する可能性があるので注意が必要です。
  • keyExtractor:

    • 説明: 各リストアイテムの一意のキーを抽出するための関数です。これはFlatListだけでなく、Reactのリストレンダリング全般にわたって非常に重要です。
    • 効果: Reactがアイテムの追加、削除、並べ替えを効率的に追跡し、不要な再レンダリングを避けることができます。キーが不適切だと、リストのパフォーマンスが大幅に低下したり、状態が正しく保持されなかったりする原因になります。
    • : keyExtractor={(item) => item.id}
  • removeClippedSubviews:

    • 説明: trueに設定すると、ビューポート外のビューをネイティブのビュー階層からデタッチします。これにより、メインスレッドでの描画処理の時間を削減し、フレーム落ちのリスクを減らすことができます。
    • 注意点: 特定の状況下ではバグを引き起こす可能性があり、推奨されない場合があります。特に、複雑なネストされたビューや、動的な高さのアイテムがある場合には注意が必要です。React Nativeのドキュメントでは、このプロパティの使用は推奨されていません。
  • updateCellsBatchingPeriod:

    • 説明: maxToRenderPerBatchによって新しいアイテムのレンダリングを開始するまでの遅延時間(ミリ秒)を指定します。デフォルトは50msです。
    • 効果: この値を調整することで、スクロール中のUIの応答性とアイテムの表示速度のバランスを取ることができます。
  • maxToRenderPerBatch:

    • 説明: スクロール中に、一度にレンダリングするアイテムの最大数を指定します。デフォルトは10です。
    • 効果: この値を大きくすると、スクロール時のブランクエリアが減りますが、一度に多くのアイテムがレンダリングされるため、JavaScriptスレッドがブロックされ、反応性が低下する可能性があります。
    • : maxToRenderPerBatch={5}
  • windowSize:

    • 説明: ビューポート(画面に表示されている領域)の上下に、どれだけのアイテムをレンダリングしておくかを制御します。デフォルト値は21(現在のビューポート+上下にそれぞれ10ビューポート分)です。
    • 効果: windowSizeを大きくすると、スクロール時にブランクエリアが見える可能性が減りますが、同時にメモリ使用量が増加し、レンダリング負荷も上がります。小さくするとメモリ使用量は減りますが、ブランクエリアが発生しやすくなります。
    • : windowSize={10} (現在のビューポートの上下にそれぞれ4ビューポート分)
  • initialNumToRender:

    • 説明: 最初にレンダリングするアイテムの数を指定します。画面に表示される最小限のアイテム数を設定することで、初回ロード時の時間を短縮できます。
    • 注意点: 少なすぎると、初回表示時に画面が空白になる「ブランクエリア」が発生する可能性があります。多すぎると初回ロードが遅くなります。適切な値は、デバイスの画面サイズやアイテムの高さによって調整する必要があります。
    • : initialNumToRender={10}

getItemLayoutが使えない(アイテムの高さが動的)場合の主な代替手段は以下の通りです。

  • measureLayoutを使用して動的に高さを測定し、scrollToOffsetでスクロールする (複雑):

    • これは非常に高度で、通常は推奨されません。各アイテムがレンダリングされた後にonLayoutコールバックでその高さを測定し、その情報を保持する、という複雑なロジックを実装する必要があります。
    • その後、scrollToIndexの代わりにscrollToOffsetを使用して、計算されたオフセットに基づいてスクロールします。しかし、これもレイアウトの変更が発生するたびに再計算が必要となり、パフォーマンスの問題を根本的に解決するものではありません。
  • ScrollViewを使用する (非推奨):

    • リストのアイテム数が非常に少なく(数十個程度)、すべてのアイテムが一度にレンダリングされてもパフォーマンスに問題がない場合にのみ検討できます。
    • ScrollViewはすべてのアイテムを一度にレンダリングするため、アイテム数が多い場合にメモリ使用量が増加し、パフォーマンスが著しく低下します。これは仮想化リスト(FlatListSectionListなど)の利点を完全に打ち消します。
    • 警告: FlatListSectionListのようなVirtualizedListベースのコンポーネントを、同じ方向のScrollViewの中にネストすることは、パフォーマンス上の問題や警告の原因となるため、避けるべきです。
  • FlashListの検討 (Shopify製):

    • FlashListは、Shopifyが開発したFlatListの代替となる高性能なリストコンポーネントです。動的な高さのアイテムに対するパフォーマンスを大幅に改善するように設計されています。
    • FlatListと非常に似たAPIを持っているため、既存のFlatListを置き換えることが比較的容易です。
    • FlashListは、getItemLayoutのような事前のサイズ情報なしに、より効率的な仮想化とメモリ管理を実現します。特に、アイテムの高さが動的で、かつリストが非常に長い場合に非常に有効です。
  • FlatListのデフォルトの動作に任せる:

    • getItemLayoutを指定しない場合、FlatListはアイテムがレンダリングされた後にそのサイズを測定します。これは、アイテムの高さが動的である場合の標準的なアプローチです。
    • 上記の「FlatListの最適化プロパティ」を最大限に活用し、初期表示数を調整したり、windowSizeを最適化したりすることで、ある程度のパフォーマンスは確保できます。

getItemLayoutが最適な選択肢であるのは、リストアイテムの高さが事前にわかっていて、固定である場合です。もしアイテムの高さが動的な場合は、以下の選択肢を検討してください。

  1. FlatListの他の最適化プロパティを最大限に活用する。
  2. 高性能なリストが必要で、特にアイテムの高さが動的である場合は、FlashListへの移行を強く検討する。
  3. リストが非常に短い場合に限り、ScrollViewも選択肢になりえますが、ほとんどのケースではFlatListが推奨されます。