React Native FlatListのkeyExtractor徹底解説:パフォーマンス最適化の鍵

2025-05-31

FlatListとは?

まず、FlatListは、大量のデータを効率的に表示するためのReact Nativeのコンポーネントです。ScrollViewとは異なり、画面に表示されているアイテムのみをレンダリングし、スクロールに応じて不要なコンポーネントを再利用することで、メモリ使用量を最適化し、スムーズなスクロールを実現します。

なぜkeyExtractorが必要なのか?

React(およびReact Native)がリストをレンダリングする際、各アイテムに一意の「キー」を持たせることを強く推奨しています。このキーは、Reactがリスト内のアイテムが追加、削除、または順序変更されたときに、どのアイテムが変更されたかを効率的に識別するために使用されます。

キーがない場合や一意でない場合、Reactはリストの変更を正しく追跡できず、不要な再レンダリングが発生したり、UIの挙動が不安定になったりする可能性があります。例えば、リスト内のアイテムの順序が入れ替わったときに、キーがないとReactはアイテムが入れ替わったことを認識できず、リスト全体を再レンダリングしてしまうことがあります。これはパフォーマンスの低下につながります。

keyExtractorの役割

keyExtractorは、FlatListに渡すデータ配列の各アイテムから、一意のキーを抽出する方法を指示する関数です。

デフォルトでは、FlatListはデータアイテムのkeyプロパティ、次にidプロパティをキーとして使用しようとします。しかし、もしあなたのデータにkeyidという名前のプロパティがない場合や、異なる名前(例: _iditemIdなど)で一意の識別子を持っている場合に、keyExtractorを使って明示的にキーを抽出するロジックを提供する必要があります。

keyExtractorの書式

keyExtractorは、item(現在のアイテム)とindex(アイテムのインデックス)の2つの引数を受け取り、一意の文字列を返す関数です。

<FlatList
  data={yourDataArray}
  renderItem={({ item }) => <Text>{item.name}</Text>}
  keyExtractor={(item, index) => item.uniqueId.toString()} // ここがkeyExtractor
/>


もしあなたのデータが以下のような形式だとします。

const myData = [
  { productId: 'a1', name: 'りんご' },
  { productId: 'b2', name: 'みかん' },
  { productId: 'c3', name: 'バナナ' },
];

この場合、productIdが一意の識別子なので、keyExtractorは次のようになります。

<FlatList
  data={myData}
  renderItem={({ item }) => <Text>{item.name}</Text>}
  keyExtractor={(item) => item.productId} // item.productIdをキーとして使用
/>

もしデータがプリミティブ型(文字列や数値の配列)の場合、インデックスをキーとして使用することもできます。ただし、リストのアイテムの順序が変更される可能性がある場合は、インデックスをキーとして使用することは推奨されません。

const names = ['太郎', '花子', '一郎'];

<FlatList
  data={names}
  renderItem={({ item }) => <Text>{item}</Text>}
  keyExtractor={(item, index) => String(index)} // インデックスをキーとして使用
/>

keyExtractorを適切に設定しないと、React Nativeは「VirtualizedList: missing keys」のような警告を表示することがあります。これにより、リストが正しく追跡されず、スクロール時のパフォーマンスの問題や、アイテムの再レンダリングに関する予期せぬ挙動が発生する可能性があります。

  • 重要性
    • Reactがリストの変更(追加、削除、順序変更)を効率的に追跡し、必要な部分のみを再レンダリングするのに役立つ。
    • これにより、リストのスクロールパフォーマンスが向上し、UIのちらつきや予期せぬ挙動を防ぐことができる。
    • 一意で安定したキーを使用することが推奨される(データ自体に一意なIDがある場合はそれを使用)。
  • 機能
    FlatListに渡されるデータ配列の各アイテムから、一意のキー(文字列)を抽出する関数を定義する。
  • 目的
    FlatListが各リストアイテムを一意に識別できるようにし、パフォーマンスと安定性を向上させる。


「VirtualizedList: missing keys for items...」または「Each child in a list should have a unique "key" prop」警告

これはFlatListを使用する上で最も頻繁に見られる警告です。Reactがリスト内のアイテムを一意に識別するためのキーを見つけられない場合に表示されます。

原因

  • renderItem内で別途keyプロップを設定している(誤った使い方)
    FlatListkeyExtractorを通じてキーを管理するため、renderItem内で個々のコンポーネントにkeyプロップを直接設定する必要はありません。むしろ、設定すると混乱を招くことがあります。
  • キーがユニークでない
    複数のアイテムが同じキーを持っている場合。
  • 返されるキーが文字列ではない
    keyExtractorは文字列を返す必要がありますが、数値などをそのまま返している場合。
  • keyExtractorが設定されていないか、正しく設定されていない
    • FlatListはデフォルトでデータアイテムのkeyプロパティ、次にidプロパティをキーとして使用しようとしますが、これらのプロパティがデータに存在しない場合。
    • keyExtractorを定義しているものの、その関数が実際には一意の値を返していない場合。

トラブルシューティング

  1. keyExtractorが定義されているか確認する
    <FlatList
      data={myData}
      renderItem={({ item }) => <MyListItem item={item} />}
      keyExtractor={(item, index) => item.id.toString()} // ここを必ず確認
    />
    
  2. キーとして使用するプロパティがデータ内で一意であることを確認する
    ほとんどの場合、サーバーから取得したデータにはid_idのような一意の識別子が含まれています。それらを活用しましょう。
    const myData = [
      { id: 'uuid-1', name: 'アイテムA' },
      { id: 'uuid-2', name: 'アイテムB' },
      // ...
    ];
    keyExtractor={(item) => item.id} // item.id が一意であると仮定
    
  3. キーが文字列として返されていることを確認する
    数値型のIDを使用している場合は、toString()を使って文字列に変換します。
    keyExtractor={(item) => item.id.toString()} // 数値IDの場合
    
  4. インデックスをキーとして使用する際の注意
    • データが静的で、リストの並び順が変更されたり、アイテムが追加・削除されたりすることがない場合にのみ、インデックスをキーとして使用できます。
    • 動的なリストでインデックスをキーとして使用すると、パフォーマンスの問題や、アイテムの表示が崩れるなどの予期せぬ挙動を引き起こす可能性があります。
    • どうしてもインデックスを使う場合は、文字列に変換します。
      keyExtractor={(item, index) => index.toString()}
      

リストのスクロールパフォーマンスが悪い / UIのちらつき

原因

  • renderItem関数がコンポーネント内で定義されている
    renderItem関数がコンポーネントのレンダリングごとに再作成されると、FlatListの最適化が効かなくなり、パフォーマンスが低下します。
  • キーが一意でない、または安定していない
    アイテムが追加・削除・並び替えられた際に、Reactが変更を正しく追跡できず、不要な再レンダリングが発生するため。特に、Math.random()などのように毎回異なる値を生成する関数をキーとして使用するのは絶対に避けるべきです。

トラブルシューティング

  1. 一意で安定したキーを使用する
    データを識別する最も信頼性の高い、変更されない値(例:DBのID)を使用します。
  2. renderItem関数をコンポーネントの外に定義するか、useCallbackでメモ化する
    // コンポーネントの外に定義
    const renderListItem = ({ item }) => <MyListItem item={item} />;
    
    function MyList() {
      // ...
      return (
        <FlatList
          data={myData}
          renderItem={renderListItem} // 外で定義した関数を使用
          keyExtractor={(item) => item.id.toString()}
        />
      );
    }
    
    またはuseCallbackを使用(関数コンポーネントの場合):
    import React, { useCallback } from 'react';
    
    function MyList() {
      // ...
      const renderListItem = useCallback(({ item }) => {
        return <MyListItem item={item} />;
      }, []); // 依存配列が空なら、この関数は一度だけ作成される
    
      return (
        <FlatList
          data={myData}
          renderItem={renderListItem}
          keyExtractor={(item) => item.id.toString()}
        />
      );
    }
    
  3. getItemLayoutプロップを使用する (アイテムの高さが一定の場合)
    これを設定すると、FlatListが各アイテムのレイアウトを動的に計算する必要がなくなり、大幅なパフォーマンス改善が見込めます。
    <FlatList
      data={myData}
      renderItem={renderListItem}
      keyExtractor={(item) => item.id.toString()}
      getItemLayout={(data, index) => (
        { length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index }
      )}
    />
    
    ITEM_HEIGHTは各アイテムの固定の高さです。
  4. removeClippedSubviewsプロップをtrueにする
    FlatListが画面外のビューをアンマウントしてメモリを節約します。ただし、これによってまれに表示上の問題が発生することもあるので、注意が必要です。

データソースの変更がUIに反映されない

データは更新されているはずなのに、FlatListの表示が古いままになることがあります。

原因

  • keyExtractorが安定したキーを返していない
    前述のパフォーマンス問題と同様に、キーが頻繁に変わることで、Reactがアイテムの変更を正しく追跡できないため。
  • dataプロップが正しく更新されていない
    Reactのコンポーネントは、propsやstateが変更された場合にのみ再レンダリングされます。data配列自体が参照として変更されていない場合(配列内の要素が変更されただけで、配列の参照は同じままの場合)、FlatListは再レンダリングの必要性を認識しないことがあります。

トラブルシューティング

  1. 新しい配列参照を渡すことでdataプロップを更新する
    配列の内容を変更するだけでなく、新しい配列を作成してFlatListに渡すようにします。
    // 例: アイテムを削除する場合
    const updatedData = myData.filter(item => item.id !== deletedItemId);
    setMyData(updatedData); // stateを更新して新しい配列を渡す
    
  2. keyExtractorが安定したキーを返していることを再確認する
    これが最も重要です。

keyExtractorの引数itemがundefinedになる

keyExtractor関数内でitemプロパティにアクセスしようとしたときにエラーが発生する場合。

原因

  • データが非同期で取得され、まだ利用可能になっていない
    初回レンダリング時にdatanullundefinedであるにもかかわらず、keyExtractorが実行されてしまう。
  • dataプロップに渡されたデータが期待される形式ではない
    data配列の各要素がオブジェクトではなく、プリミティブ値(文字列や数値)である場合や、データが空である場合。

トラブルシューティング

  1. dataプロップに正しいデータが渡されているか確認する
    // dataが[ { id: 1, name: 'A' }, { id: 2, name: 'B' } ] のような形式であることを確認
    console.log(myData); // データの内容を確認
    
  2. データのロード状態を考慮する
    データが非同期でロードされる場合は、データが利用可能になるまでFlatListをレンダリングしないようにするか、dataプロップに空の配列を渡します。
    {myData && myData.length > 0 ? (
      <FlatList
        data={myData}
        renderItem={({ item }) => <MyListItem item={item} />}
        keyExtractor={(item) => item.id.toString()}
      />
    ) : (
      <Text>データがありません</Text>
    )}
    
    または、data={myData || []}のようにデフォルト値を設定することもできます。
  3. keyExtractor内でnull/undefinedチェックを行う
    keyExtractor={(item, index) => (item && item.id ? item.id.toString() : index.toString())}
    
    ただし、後者のindex.toString()は一時的な回避策であり、根本的なデータ設計を見直す方が良いでしょう。

リストアイテム内の特定のコンポーネントのプロパティが変更されても、UIが更新されない場合。

原因

  • keyExtractorの不適切さ
    キーが安定していないと、Reactがアイテムを再利用すべきか、新しく作成すべきかを判断しにくくなり、更新の不具合につながることがあります。
  • FlatListがPureComponentであることによる最適化の副作用
    FlatListPureComponentとして実装されており、propsが浅い比較(shallow comparison)で変更がなければ再レンダリングされません。renderItemの内部で参照型のプロパティ(オブジェクトや配列)が変更されても、その参照自体が変わっていなければ、FlatListは変更を検知しません。
  1. extraDataプロップを使用する
    renderItemが依存する親コンポーネントのstateやpropsがあり、それらが更新されたときにFlatList全体を再レンダリングさせたい場合にextraDataプロップにそれらの値を渡します。
    const [selectedId, setSelectedId] = useState(null);
    
    return (
      <FlatList
        data={myData}
        renderItem={({ item }) => (
          <MyListItem
            item={item}
            isSelected={item.id === selectedId}
            onPress={() => setSelectedId(item.id)}
          />
        )}
        keyExtractor={(item) => item.id.toString()}
        extraData={selectedId} // selectedIdが変更されたらFlatListも再レンダリング
      />
    );
    
  2. renderItem内のコンポーネントをReact.memoでメモ化する
    MyListItemコンポーネント自体が不要な再レンダリングを避けるように、React.memoでラップすることを検討します。ただし、この場合もkeyExtractorは適切である必要があります。
    const MyListItem = React.memo(({ item, isSelected, onPress }) => {
      // ...
    });
    


FlatListの基本構造

まず、FlatListの基本的な構造を見てみましょう。

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

const DATA = [
  { id: '1', title: '最初のアイテム' },
  { id: '2', title: '次のアイテム' },
  { id: '3', title: 'もっと次のアイテム' },
  // ...さらに多くのアイテム
];

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

const App = () => {
  return (
    <FlatList
      data={DATA} // 表示するデータ配列
      renderItem={({ item }) => <Item title={item.title} />} // 各アイテムのレンダリング方法
      // keyExtractor={...} // ここにkeyExtractorを設定します
    />
  );
};

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

export default App;

この基本構造に対して、keyExtractorをどのように設定するかを見ていきます。

例1:idプロパティをキーとして使用する (推奨)

最も一般的なケースで、データ配列の各オブジェクトが一意のidプロパティを持っている場合です。

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

const DATA = [
  { id: 'item-1', title: 'リンゴ' },
  { id: 'item-2', title: 'ミカン' },
  { id: 'item-3', title: 'バナナ' },
  { id: 'item-4', title: 'イチゴ' },
  { id: 'item-5', title: 'ブドウ' },
];

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

const App = () => {
  return (
    <FlatList
      data={DATA}
      renderItem={({ item }) => <Item title={item.title} />}
      keyExtractor={(item) => item.id} // 各アイテムの 'id' プロパティをキーとして使用
    />
  );
};

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

export default App;

解説

  • FlatListは、このidを使って各アイテムのDOM要素にキーを設定し、リストの変更を効率的に追跡します。
  • keyExtractor={(item) => item.id}: この行がkeyExtractorの設定です。
    • item: FlatListdata配列から取り出された現在のアイテムオブジェクトを指します。
    • item.id: 現在のアイテムのidプロパティの値を返します。このidは、データ内で一意である必要があります。

例2:_idなど、異なるプロパティ名をキーとして使用する

データソースによっては、一意の識別子がid以外の名前(例: MongoDBの_idや、uuidproductIdなど)で提供されることがあります。

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

const PRODUCTS = [
  { _id: 'prod-001', name: 'MacBook Air M3' },
  { _id: 'prod-002', name: 'iPad Pro M4' },
  { _id: 'prod-003', name: 'iPhone 15 Pro' },
];

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

const App = () => {
  return (
    <FlatList
      data={PRODUCTS}
      renderItem={({ item }) => <ProductItem name={item.name} />}
      keyExtractor={(item) => item._id} // '_id' プロパティをキーとして使用
    />
  );
};

const styles = StyleSheet.create({
  item: {
    backgroundColor: '#a2d2ff',
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16,
  },
  title: {
    fontSize: 24,
    color: '#333',
  },
});

export default App;

解説

  • 重要なのは、そのプロパティの値が一意であることです。プロパティ名自体は何でも構いません。
  • keyExtractor={(item) => item._id}: この場合、item._idが各プロダクトを一意に識別するキーとして使用されます。

例3:IDが数値の場合、文字列に変換してキーとして使用する

keyExtractorは文字列のキーを期待します。もしIDが数値型の場合、toString()を使って文字列に変換する必要があります。

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

const USERS = [
  { userId: 101, username: 'Alice' },
  { userId: 102, username: 'Bob' },
  { userId: 103, username: 'Charlie' },
];

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

const App = () => {
  return (
    <FlatList
      data={USERS}
      renderItem={({ item }) => <UserItem username={item.username} />}
      keyExtractor={(item) => item.userId.toString()} // 数値IDを文字列に変換
    />
  );
};

const styles = StyleSheet.create({
  item: {
    backgroundColor: '#bde0fe',
    padding: 15,
    marginVertical: 6,
    marginHorizontal: 12,
  },
  title: {
    fontSize: 20,
    color: '#0056b3',
  },
});

export default App;

解説

  • keyExtractor={(item) => item.userId.toString()}: userIdが数値なので、toString()を使って文字列に変換しています。これを忘れると、「VirtualizedList: missing keys...」のような警告が出る可能性があります。

データ配列のオブジェクトに一意のIDプロパティがない場合、そのアイテムのインデックスをキーとして使用することができます。しかし、これは推奨されません

  • なぜ推奨されないのか?:
    • リストのアイテムが追加、削除、または並び替えられた場合、インデックスは変化します。
    • Reactは、キーが変化するとそのアイテムが「別物」になったと判断し、不要な再レンダリングを引き起こしたり、状態がリセットされたり、UIが予期せぬ挙動を示す可能性があります。
    • 例えば、リスト内のアイテムの順序を入れ替えただけで、すべてのアイテムが再レンダリングされることがあります。

使用が許容される非常に限定的なケース

  • リストが静的で、今後一切、アイテムの追加・削除・並び替えが行われないことが保証されている場合。
import React from 'react';
import { FlatList, Text, View, StyleSheet } from 'react-native';

const COLORS = ['Red', 'Green', 'Blue', 'Yellow', 'Purple']; // プリミティブ型の配列

const ColorItem = ({ colorName }) => (
  <View style={[styles.item, { backgroundColor: colorName.toLowerCase() }]}>
    <Text style={styles.title}>{colorName}</Text>
  </View>
);

const App = () => {
  return (
    <FlatList
      data={COLORS}
      renderItem={({ item, index }) => <ColorItem colorName={item} />}
      keyExtractor={(item, index) => index.toString()} // インデックスをキーとして使用(非推奨)
    />
  );
};

const styles = StyleSheet.create({
  item: {
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16,
    borderRadius: 8,
  },
  title: {
    fontSize: 28,
    color: 'white',
    fontWeight: 'bold',
    textAlign: 'center',
  },
});

export default App;

解説

  • この例のように、データがプリミティブな文字列の配列で、かつ順序が変わらないことが確実な場合にのみ、インデックスの使用を検討できます。しかし、可能な限り一意のIDをデータに持たせるように設計するべきです。
  • keyExtractor={(item, index) => index.toString()}: keyExtractorは第2引数としてindexも受け取ります。ここではそのindexを文字列に変換してキーとしています。

keyExtractorの主な目的は、FlatListのパフォーマンスと安定性を高めることです。

  • 避けるべき方法
    Math.random()のように毎回異なる値を生成する関数をキーとして使用すること。インデックスをキーとして使用することも、リストが動的である場合は避けるべきです。
  • 注意点
    keyExtractor文字列を返す必要があります。数値IDの場合はtoString()で変換する。
  • 最も推奨される方法
    データソースの各アイテムに存在する一意で安定したIDプロパティ(例: id, _id, uuidなど)をキーとして使用する。


React NativeのFlatListにおいて、keyExtractorはリストの要素を一意に識別するために必須のプロパティです。これは、Reactのリストレンダリングの仕組みにおいて、要素の追加、削除、並び替えを効率的に行うための基盤となります。

そのため、「keyExtractorの代替方法」という表現は、厳密には「keyExtractorを使わない方法」ではなく、「keyExtractorの内部で、どのようなロジックを使ってキーを生成するか」のバリエーションを指すことが多いです。

keyExtractorの代替ロジック(keyExtractor自体は使用する)

これは、keyExtractorプロパティ自体は使用しつつ、その内部でキーを生成するロジックのバリエーションを指します。

  1. データに存在する一意のIDを使用する(最も推奨される方法) これが最もパフォーマンスが高く、安定した方法です。データがAPIなどから提供される場合、通常は一意の識別子(id_iduuidproductIdなど)が含まれています。

    <FlatList
      data={myItems}
      renderItem={({ item }) => <MyItem item={item} />}
      keyExtractor={(item) => item.id.toString()} // もしくは item._id, item.uuid など
    />
    
    • データオブジェクト自体に存在する、変更されない一意の値をキーとして使います。
    • 数値の場合は.toString()で文字列に変換します。
  2. インデックスをキーとして使用する(非推奨だが、限定的な状況で可) データが静的で、リストの要素が追加、削除、または並び替えられることが絶対にない場合にのみ、インデックスをキーとして使用することが許容されます。動的なリストでこれを行うと、パフォーマンスの問題や、UIのバグ(例:入力値がリセットされる)を引き起こす可能性があります。

    <FlatList
      data={myStaticItems}
      renderItem={({ item, index }) => <MyItem item={item} />}
      keyExtractor={(item, index) => index.toString()}
    />
    

    解説

    • keyExtractorの第二引数indexを使用します。
    • インデックスは数値なので、必ずtoString()で文字列に変換します。
    • 本当にこれが静的なリストなのか、よく考えてから使用してください。
  3. 複数のプロパティを組み合わせて複合キーを生成する データに単一の一意なIDがない場合、複数のプロパティを連結して一意なキーを作成することができます。

    const myData = [
      { type: 'fruit', name: 'apple' },
      { type: 'fruit', name: 'banana' },
      { type: 'vegetable', name: 'carrot' },
    ];
    
    <FlatList
      data={myData}
      renderItem={({ item }) => <MyItem item={item} />}
      keyExtractor={(item) => `<span class="math-inline">\{item\.type\}\-</span>{item.name}`} // typeとnameを組み合わせる
    />
    

    解説

    • この方法は、組み合わせるプロパティが結果として一意になることを保証できる場合にのみ有効です。上記の例では、「apple」という名前がフルーツと野菜の両方に存在しないことを保証する必要があります。
  4. キー生成関数をuseCallbackでメモ化する これはkeyExtractorのロジック自体ではなく、keyExtractor関数のパフォーマンスを最適化するための方法です。関数コンポーネントでkeyExtractorをインラインで定義すると、親コンポーネントが再レンダリングされるたびにkeyExtractor関数も再作成されてしまいます。これを避けるためにuseCallbackでメモ化します。

    import React, { useCallback } from 'react';
    import { FlatList } from 'react-native';
    
    const MyList = () => {
      const myData = [{ id: '1', name: 'Test' }];
    
      const memoizedKeyExtractor = useCallback(
        (item) => item.id.toString(),
        [] // 依存配列が空なので、一度だけ作成される
      );
    
      return (
        <FlatList
          data={myData}
          renderItem={({ item }) => <MyItem item={item} />}
          keyExtractor={memoizedKeyExtractor}
        />
      );
    };
    

    解説

    • useCallbackを使用することで、memoizedKeyExtractor関数は依存配列[]が変更されない限り再生成されません。これにより、FlatListの内部最適化がより効果的に機能します。

もし「FlatListkeyExtractorに関する代替方法」が、FlatList自体を使わない場合の代替手段を指しているのであれば、以下の方法が挙げられます。

  1. ScrollViewmap()関数を使用する これは最も基本的な方法で、配列のmap()関数を使って各要素をレンダリングし、それらをScrollViewでラップします。

    import React from 'react';
    import { ScrollView, Text, View, StyleSheet } from 'react-native';
    
    const DATA = [
      { id: '1', title: 'アイテムA' },
      { id: '2', title: 'アイテムB' },
      { id: '3', title: 'アイテムC' },
      // ...
    ];
    
    const App = () => {
      return (
        <ScrollView style={styles.container}>
          {DATA.map((item) => (
            // ここでReactのキーが必要になります
            <View key={item.id} style={styles.item}>
              <Text style={styles.title}>{item.title}</Text>
            </View>
          ))}
        </ScrollView>
      );
    };
    
    const styles = StyleSheet.create({
      container: {
        flex: 1,
        paddingTop: 22,
      },
      item: {
        backgroundColor: '#e0e0e0',
        padding: 20,
        marginVertical: 8,
        marginHorizontal: 16,
      },
      title: {
        fontSize: 18,
      },
    });
    
    export default App;
    

    注意点

    • ScrollViewは、すべてのアイテムを一度にレンダリングします。そのため、アイテム数が少ない(数十個程度まで)場合は問題ありませんが、大量のアイテム(数百、数千など)を扱う場合は、メモリ使用量とパフォーマンスが悪化します。
    • map()関数でレンダリングする各要素には、必ず一意のkeyプロップを設定する必要があります。これはFlatListkeyExtractorが内部で行っていることと同様の目的です。
  2. SectionListを使用する もしデータがセクションに分かれている場合、SectionListはより適切な選択肢です。SectionListFlatListと同様に、キー抽出のためにkeyExtractor(またはデフォルトのkey/id)を使用します。

    import React from 'react';
    import { SectionList, Text, View, StyleSheet } from 'react-native';
    
    const SECTIONS = [
      {
        title: 'フルーツ',
        data: [{ id: 'f1', name: 'リンゴ' }, { id: 'f2', name: 'バナナ' }],
      },
      {
        title: '野菜',
        data: [{ id: 'v1', name: 'ニンジン' }, { id: 'v2', name: 'タマネギ' }],
      },
    ];
    
    const App = () => {
      return (
        <SectionList
          sections={SECTIONS}
          keyExtractor={(item, index) => item.id + index} // ここもkeyExtractorが必要
          renderItem={({ item }) => (
            <View style={styles.item}>
              <Text>{item.name}</Text>
            </View>
          )}
          renderSectionHeader={({ section: { title } }) => (
            <Text style={styles.header}>{title}</Text>
          )}
        />
      );
    };
    
    const styles = StyleSheet.create({
      item: {
        padding: 10,
        marginVertical: 4,
        marginHorizontal: 16,
        backgroundColor: '#f0f0f0',
      },
      header: {
        fontSize: 24,
        backgroundColor: '#fff',
        padding: 10,
      },
    });
    
    export default App;
    

    解説

    • SectionListは内部的にVirtualizedListを使用しており、FlatListと同様の仮想化によるパフォーマンス最適化が行われます。
    • ここでも、keyExtractorは各アイテム(セクション内のデータ)に対して一意のキーを生成するために使用されます。

FlatListkeyExtractorは、React Nativeにおける大規模リスト表示のパフォーマンスと安定性を確保するための中心的な要素であり、これを使わないこと自体は推奨されません。むしろ、keyExtractorどのように効果的に使うかが重要になります。