FlatList#data

2025-05-31

FlatList#dataとは

FlatListは、大量のデータを効率的に表示するためのReact Nativeのコンポーネントです。特に、スクロール可能なリスト表示に適しています。このFlatListを使用する際に、表示したいデータの集合(リスト) を指定するのが、dataプロパティの役割です。

役割と重要性

dataプロパティは、FlatListにとって必須のプロパティです。これがないと、FlatListは何を表示すればよいかわかりません。

具体的には、以下のような特徴があります。

  1. 表示するデータの提供: dataには、表示したいアイテムの配列(Array)を渡します。例えば、ユーザーの投稿リスト、商品リスト、メッセージリストなど、どのようなデータでも構いません。

    const myData = [
      { id: '1', title: '最初のアイテム' },
      { id: '2', title: '次のアイテム' },
      { id: '3', title: '最後のアイテム' },
      // ...さらに多くのアイテム
    ];
    
    <FlatList
      data={myData} // ここでデータを渡す
      renderItem={({ item }) => <Text>{item.title}</Text>}
      keyExtractor={item => item.id}
    />
    
  2. パフォーマンスの最適化: FlatListは、dataプロパティに渡された配列全体を一度に描画するわけではありません。代わりに、画面に表示されている(または表示されそうな)アイテムだけをレンダリングすることで、メモリ使用量を抑え、パフォーマンスを向上させます。これを「仮想化 (Virtualization)」と呼びます。

  3. データの更新と再レンダリング: dataプロパティに渡す配列が更新されると、FlatListは自動的に変更を検知し、リストを再レンダリングします。これにより、リアルタイムなデータの追加、削除、変更がスムーズに反映されます。

  • 各アイテムの一意なキー: FlatListは、リスト内の各アイテムを一意に識別するためにkeyExtractorプロパティを必要とします。通常、data配列内の各オブジェクトには、idkeyといった一意な識別子を含めることが推奨されます。もしデータに一意なキーがない場合でも、keyExtractorを使ってインデックスなどから一意なキーを生成する必要があります。


データが表示されない、または一部しか表示されない

よくある原因

  • データがState/Propsの更新時に変更されていない: Reactのコンポーネントは、StateやPropsが「変更された」と判断しないと再レンダリングされません。もしdata配列の中身だけが変わって、配列そのものの参照が変わっていない場合、FlatListは更新を検知できません。
  • スタイルやレイアウトの問題: FlatListやその親コンポーネントのスタイル(特にflexプロパティなど)が原因で、リストが描画されていない場合があります。
  • keyExtractorが正しく定義されていない、または一意でない: FlatListは各アイテムを一意に識別するためにkeyExtractorを必要とします。これが欠けているか、一意でないキーを返していると、リストの更新時に問題が発生したり、警告が表示されたりします。
  • renderItemが正しく定義されていない: dataに渡された各アイテムをどのようにレンダリングするかを定義するrenderItemプロパティが正しく設定されていない場合、何も表示されません。
  • dataが配列ではない: FlatListdataプロパティは必ず配列である必要があります。オブジェクトや他のデータ型を渡すと表示されません。
  • dataに空の配列が渡されている: 初期状態やAPIからのデータ取得に失敗した場合など、dataが空の配列になっている可能性があります。

トラブルシューティング

  • データの参照を更新する:
    • データを変更する際(追加、削除、編集など)は、既存の配列を直接変更するのではなく、新しい配列を作成してdataプロパティに渡すようにします。例えば、[...oldData, newItem]のようにスプレッド構文を使ったり、map, filterなどを使って新しい配列を生成します。
    • ステート管理ライブラリ(Reduxなど)を使用している場合は、イミュータブルなデータ更新が行われているか確認します。
    • FlatListがPureComponentであるため、data以外のProp(例: renderItemで使われている親コンポーネントのStateなど)が更新されたことをFlatListに伝える必要がある場合は、extraDataプロパティを使用します。extraData={this.state.someRelevantState}のように設定すると、extraDataの値が変更されたときにFlatListが再レンダリングされます。
  • スタイル・レイアウトの調整:
    • FlatListの親コンポーネントにflex: 1などを設定して、FlatListが描画領域を十分に確保できるようにします。
    • FlatList自体にflex: 1heightなどを明示的に指定して、表示領域を確保します。
  • keyExtractorの確認:
    • keyExtractorが各アイテムから一意な文字列(IDなど)を返していることを確認します。
    • もしデータに一意なIDがない場合、keyExtractor={(item, index) => index.toString()}のように、インデックスをキーとして使用することもできますが、これはデータの追加・削除・並べ替えの際にパフォーマンスや動作に問題を引き起こす可能性があるため、推奨されません。できる限り一意なIDを持つようにデータを設計しましょう。
  • renderItemの確認:
    • renderItem関数内で、item(各データ)が正しくアクセスされているか確認します(例: renderItem={({ item }) => <Text>{item.title}</Text>})。
    • 単純なTextコンポーネントなどを表示してみて、基本的なレンダリングが行われているか確認します。
  • dataの内容を確認する:
    • console.log(data)で、dataプロパティに渡している変数の内容が期待通りに配列であり、データが含まれているか確認します。
    • APIからデータを取得している場合は、APIレスポンスが正しい形式(配列)で返ってきているか確認します。

パフォーマンスの問題(スクロールがカクつく、空白が表示される)

よくある原因

  • アイテムの高さが可変でgetItemLayoutが使われていない: FlatListは各アイテムの高さが固定であることを前提にパフォーマンスを最適化しています。アイテムの高さが動的に変わる場合、getItemLayoutプロパティを設定しないと、正確なスクロール位置の計算ができず、スクロールがスムーズでなくなったり、空白が表示されたりすることがあります。
  • dataの頻繁な更新: dataプロパティが非常に頻繁に更新されると、FlatListの再レンダリングが頻発し、パフォーマンスが低下します。
  • renderItemが重い: renderItem内で複雑な計算を行ったり、多くのコンポーネントをレンダリングしたり、最適化されていない画像を表示したりすると、パフォーマンスが低下します。
  • keyExtractorが不適切: 一意でないキーを使用している、または毎回新しいキーを生成している場合、Reactがリストアイテムの変更を正しく追跡できず、不要な再レンダリングが発生します。

トラブルシューティング

  • initialNumToRendermaxToRenderPerBatchなどの調整:
    • FlatListのパフォーマンス関連のProps(initialNumToRender, maxToRenderPerBatch, windowSize, removeClippedSubviewsなど)を調整することで、レンダリングされるアイテムの数を制御し、パフォーマンスを向上させることができます。ただし、これらの値を誤って設定すると、かえって問題を引き起こす可能性もあるため、注意が必要です。
  • インライン関数を避ける:
    • renderItemなどの関数は、コンポーネントの外で定義するか、useCallbackフックを使用してメモ化します。
    const renderItem = useCallback(({ item }) => {
      return <MyListItem item={item} />;
    }, []); // 依存配列は、この関数が依存する値を含める
    
    <FlatList
      data={myData}
      renderItem={renderItem}
      keyExtractor={...}
    />
    
  • getItemLayoutを設定する:
    • アイテムの高さが固定であれば、getItemLayoutプロパティを設定することで、FlatListが事前にレイアウトを計算できるようになり、パフォーマンスが大幅に向上します。
    const ITEM_HEIGHT = 50; // アイテムの固定の高さ
    
    <FlatList
      data={myData}
      renderItem={...}
      keyExtractor={...}
      getItemLayout={(data, index) => (
        {length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index}
      )}
    />
    
    • アイテムの高さが可変の場合は、estimatedItemSizeを使うことも検討します(FlashListの方がより柔軟に扱えます)。
  • dataの更新頻度を抑える:
    • State更新のバッチ処理や、必要な時のみデータを更新するロジックを検討します。
    • useMemouseCallbackフックを使って、datarenderItemなどの参照が変わらないようにします。
  • renderItemの最適化:
    • renderItemでレンダリングされるアイテムコンポーネントをReact.memoでラップするか、PureComponentとして定義して、Propsが変更されない限り再レンダリングされないようにします。
    • renderItem内で複雑なロジックや重い処理を行わないようにします。
    • 画像を使用する場合は、react-native-fast-imageのような画像をキャッシュするライブラリの使用を検討します。
  • keyExtractorを最適化する:
    • 最も重要な点は、各アイテムが持つ一意のIDkeyExtractorで返すことです。
    • 常に同じキーを返している、またはランダムな値をキーにしている場合は修正が必要です。

データが更新されてもFlatListが再レンダリングされない

よくある原因

  • extraDataの欠如: FlatListrenderItem関数内で使用されているデータが、dataプロパティに含まれるアイテム自体ではなく、親コンポーネントのStateや別のPropsに依存している場合、FlatListはその変更を自動的に検知できません。
  • data配列の参照が変わっていない: Reactは、PropsやStateが浅い比較(shallow comparison)によって変更されたと判断した場合にのみコンポーネントを再レンダリングします。配列の中身だけが変わっても、配列オブジェクトそのものの参照が変わっていないと、FlatListは更新を検知しません。
  • extraDataの使用:
    • FlatListrenderItemで、dataの各アイテムとは別に、リスト全体のState(例: 選択中のアイテムのIDなど)に応じてアイテムの見た目を変更したい場合などに使用します。
    const [selectedId, setSelectedId] = useState(null);
    
    <FlatList
      data={myData}
      renderItem={({ item }) => (
        <MyListItem item={item} isSelected={item.id === selectedId} />
      )}
      keyExtractor={item => item.id}
      extraData={selectedId} // selectedIdが変わるとFlatListが再レンダリングされる
    />
    
  • 常に新しい配列を渡す:
    • dataを更新する際は、必ず新しい配列を作成してuseStateのセッター関数に渡します。
    // 悪い例:既存の配列を直接変更
    const updateItem = (itemId, newTitle) => {
      const itemIndex = myData.findIndex(item => item.id === itemId);
      if (itemIndex > -1) {
        myData[itemIndex].title = newTitle; // 参照は変わらない
        setMyData(myData); // FlatListは更新を検知しない
      }
    };
    
    // 良い例:新しい配列を作成して更新
    const updateItem = (itemId, newTitle) => {
      setMyData(prevData =>
        prevData.map(item =>
          item.id === itemId ? { ...item, title: newTitle } : item
        )
      ); // 新しい配列が生成されるため、FlatListは更新を検知
    };
    


FlatListは、そのdataプロパティに渡された配列を基にリストアイテムをレンダリングします。ここでは、基本的な使い方から、データの追加・更新・削除といった動的な操作、さらにパフォーマンス最適化のための例までを解説します。

基本的なFlatListの使用例

最も基本的な例です。静的なデータ配列をdataプロパティに渡し、renderItemで各アイテムをどのように表示するかを定義します。

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

// 表示するデータ
const DATA = [
  { id: '1', title: 'りんご' },
  { id: '2', title: 'バナナ' },
  { id: '3', title: 'みかん' },
  { id: '4', title: 'いちご' },
  { id: '5', title: 'ぶどう' },
];

// 各アイテムをレンダリングするコンポーネント
const Item = ({ title }) => (
  <View style={styles.item}>
    <Text style={styles.title}>{title}</Text>
  </View>
);

const BasicFlatList = () => {
  // renderItem関数を定義
  const renderItem = ({ item }) => (
    <Item title={item.title} />
  );

  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={DATA} // ここにデータを渡す
        renderItem={renderItem} // 各アイテムのレンダリング方法
        keyExtractor={item => item.id} // 各アイテムの一意なキー
      />
    </SafeAreaView>
  );
};

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

export default BasicFlatList;

ポイント

  • keyExtractor: 各アイテムから一意なキーを抽出する関数。これはFlatListが効率的にアイテムの追加、削除、移動を追跡するために非常に重要です。通常はデータのIDを使用します。
  • renderItem: ({ item, index, separators }) => JSX.Element の形式の関数。各データアイテム (item) を受け取り、表示するReact要素を返します。
  • data: FlatListに表示させたいアイテムの配列。

動的なデータの追加とFlatListの更新

useStateフックを使用してdataをStateとして管理することで、リストにアイテムを動的に追加できます。

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

const DynamicFlatList = () => {
  const [items, setItems] = useState([
    { id: '1', name: '最初のToDo' },
    { id: '2', name: '次のToDo' },
  ]);
  const [newTodoText, setNewTodoText] = useState('');

  const addTodo = () => {
    if (newTodoText.trim() === '') {
      Alert.alert('エラー', 'ToDoを入力してください');
      return;
    }
    const newId = (items.length + 1).toString(); // 簡単なID生成
    const newItem = { id: newId, name: newTodoText.trim() };
    
    // 既存の配列を直接変更せず、新しい配列を作成してStateを更新
    setItems(prevItems => [...prevItems, newItem]);
    setNewTodoText(''); // 入力フィールドをクリア
  };

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

  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.inputContainer}>
        <TextInput
          style={styles.textInput}
          placeholder="新しいToDoを入力"
          value={newTodoText}
          onChangeText={setNewTodoText}
        />
        <Button title="追加" onPress={addTodo} />
      </View>
      <FlatList
        data={items} // Stateとして管理されるデータ
        renderItem={renderItem}
        keyExtractor={item => item.id}
      />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 20,
  },
  inputContainer: {
    flexDirection: 'row',
    padding: 10,
    borderBottomWidth: 1,
    borderBottomColor: '#ccc',
  },
  textInput: {
    flex: 1,
    borderWidth: 1,
    borderColor: '#ddd',
    padding: 8,
    marginRight: 10,
    borderRadius: 5,
  },
  item: {
    backgroundColor: '#a2d9ff',
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16,
  },
  title: {
    fontSize: 18,
  },
});

export default DynamicFlatList;

ポイント

  • setItems(prevItems => [...prevItems, newItem]): データを更新する際に、既存の配列を直接変更する(items.push(newItem)など)のではなく、スプレッド構文などを使って新しい配列を作成し、それをsetItemsに渡すことが非常に重要です。これにより、Reactはdataプロパティの参照が変更されたことを検知し、FlatListを正しく再レンダリングします。
  • useState: itemsというStateでリストデータを管理します。

アイテムの更新と削除

アイテムの更新や削除も、新しい配列を生成してStateを更新することで行います。

import React, { useState, useCallback } from 'react';
import { FlatList, StyleSheet, Text, View, SafeAreaView, Button, TouchableOpacity, Alert } from 'react-native';

const initialItems = [
  { id: '1', name: 'React Nativeを学ぶ', completed: false },
  { id: '2', name: 'FlatListの例を試す', completed: true },
  { id: '3', name: 'Todoアプリを作成する', completed: false },
];

const UpdatableFlatList = () => {
  const [items, setItems] = useState(initialItems);

  // アイテムの完了状態を切り替える
  const toggleComplete = useCallback((id) => {
    setItems(prevItems =>
      prevItems.map(item =>
        item.id === id ? { ...item, completed: !item.completed } : item
      )
    );
  }, []);

  // アイテムを削除する
  const deleteItem = useCallback((id) => {
    Alert.alert(
      '確認',
      'このアイテムを削除しますか?',
      [
        {
          text: 'キャンセル',
          style: 'cancel',
        },
        {
          text: '削除',
          onPress: () => {
            setItems(prevItems => prevItems.filter(item => item.id !== id));
          },
          style: 'destructive',
        },
      ]
    );
  }, []);

  // 各アイテムをレンダリングするコンポーネント
  const renderItem = useCallback(({ item }) => (
    <View style={[styles.item, item.completed && styles.completedItem]}>
      <TouchableOpacity onPress={() => toggleComplete(item.id)} style={styles.itemTextContainer}>
        <Text style={[styles.title, item.completed && styles.completedText]}>
          {item.name}
        </Text>
      </TouchableOpacity>
      <Button title="削除" onPress={() => deleteItem(item.id)} color="red" />
    </View>
  ), [toggleComplete, deleteItem]); // renderItemをメモ化し、依存関係を定義

  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={items}
        renderItem={renderItem}
        keyExtractor={item => item.id}
      />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 20,
  },
  item: {
    backgroundColor: '#e0e0e0',
    padding: 15,
    marginVertical: 8,
    marginHorizontal: 16,
    borderRadius: 5,
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
  },
  completedItem: {
    backgroundColor: '#d1e7dd',
  },
  itemTextContainer: {
    flex: 1, // テキスト部分がボタンのスペースを確保するように
  },
  title: {
    fontSize: 16,
  },
  completedText: {
    textDecorationLine: 'line-through',
    color: '#888',
  },
});

export default UpdatableFlatList;

ポイント

  • useCallback: toggleCompletedeleteItemrenderItemのようなコールバック関数をuseCallbackでメモ化することで、親コンポーネントが再レンダリングされてもこれらの関数の参照が変わらないようにできます。これにより、FlatListの不要な再レンダリングを防ぎ、パフォーマンスを向上させることができます。特にrenderItemは頻繁に呼び出される可能性があるため、メモ化が推奨されます。
  • mapfilter: アイテムの更新にはmap、削除にはfilterをよく使用します。これらは新しい配列を返すため、イミュータブルな更新に適しています。

パフォーマンス最適化のためのgetItemLayoutとextraData

アイテムの高さが固定の場合、getItemLayoutを使用することでスクロールパフォーマンスを大幅に改善できます。また、data以外のStateが変更されたときにFlatListに再レンダリングを促すためにextraDataを使用する例です。

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

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

const initialItems = Array.from({ length: 1000 }, (_, i) => ({
  id: String(i + 1),
  name: `アイテム ${i + 1}`,
}));

const OptimizedFlatList = () => {
  const [items, setItems] = useState(initialItems);
  const [selectedId, setSelectedId] = useState(null); // 選択されたアイテムのID

  // アイテムを選択状態にする
  const handlePressItem = useCallback((id) => {
    setSelectedId(id);
  }, []);

  // renderItem関数をメモ化
  const renderItem = useCallback(({ item }) => {
    const isSelected = item.id === selectedId;
    return (
      <TouchableOpacity onPress={() => handlePressItem(item.id)} style={[styles.item, isSelected && styles.selectedItem]}>
        <Text style={styles.title}>{item.name}</Text>
      </TouchableOpacity>
    );
  }, [selectedId, handlePressItem]); // selectedIdが変更されたときにrenderItemも再生成される

  // getItemLayoutを設定
  const getItemLayout = useCallback((data, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  }), []);

  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={items}
        renderItem={renderItem}
        keyExtractor={item => item.id}
        getItemLayout={getItemLayout} // 固定高さアイテムのパフォーマンス最適化
        extraData={selectedId} // selectedIdが変わったときにFlatListを再レンダリングさせる
        initialNumToRender={10} // 初回にレンダリングするアイテム数
        windowSize={21} // レンダリングされる範囲(ビューポートの倍数)
      />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 20,
  },
  item: {
    backgroundColor: '#f0f0f0',
    padding: 15,
    marginVertical: 2, // マージンを小さくして詰める
    marginHorizontal: 16,
    borderRadius: 5,
    height: ITEM_HEIGHT, // 固定の高さ
    justifyContent: 'center',
  },
  selectedItem: {
    backgroundColor: '#a0a0ff', // 選択されたアイテムの背景色
  },
  title: {
    fontSize: 16,
  },
});

export default OptimizedFlatList;
  • initialNumToRenderwindowSize: 大量のデータを扱う際に、これらのプロパティを調整することで、初期レンダリング数やメモリ使用量を制御し、パフォーマンスをさらに最適化できます。
  • extraData: dataプロパティが変更されていないが、FlatListのアイテム表示に影響を与える可能性のある別のState(この例ではselectedId)がある場合に設定します。extraDataの値が変更されると、FlatListは再レンダリングを検討します。
  • getItemLayout: dataindexを受け取り、{ length: itemHeight, offset: itemOffset, index: index }の形式のオブジェクトを返します。これによりFlatListは各アイテムの正確な位置を事前に計算でき、スクロール時の空白表示(ブランクスペース)を防ぎ、スムーズなスクロールを実現します。


FlatList#data の代替プログラミング方法

FlatListは、dataプロパティに渡された単一の配列から効率的なリストを生成します。代替手段は、この「単一の配列」という制約や、FlatListが提供する特定の最適化が不要、または他の機能が必要な場合に考慮されます。

ScrollView を使用する

ScrollViewは、すべての内容を一度にレンダリングするシンプルなスクロールコンテナです。

FlatListとの違い

  • シンプルな使用: FlatListのようにdataプロパティやrenderItemkeyExtractorといった特定のプロパティを設定する必要がなく、HTMLのdivのような感覚で使えるため、非常にシンプルです。
  • 仮想化(Virtualization)がない: ScrollViewは、その中にあるすべての子要素を一度にレンダリングします。これは、要素の数が少ない場合には問題ありませんが、要素の数が多くなるとパフォーマンス上の問題(メモリ使用量の増加、UIのフリーズなど)を引き起こします可能性があります。

どのような場合に使うか

  • 内部のコンテンツが頻繁に変わらない場合: パフォーマンス最適化がそこまで重要でない場合に。
  • アイテム数が少ない場合: 10個程度までのアイテムしかないリストであれば、ScrollViewで十分なことが多いです。

コード例

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

const DATA = [
  { id: '1', title: '製品A' },
  { id: '2', title: '製品B' },
  { id: '3', title: '製品C' },
  // ...少数のみ
];

const ScrollViewExample = () => {
  return (
    <SafeAreaView style={styles.container}>
      <ScrollView style={styles.scrollView}>
        <Text style={styles.header}>お知らせ</Text>
        <Text style={styles.infoText}>
          最新のセール情報が更新されました!ぜひご確認ください。
        </Text>
        <Text style={styles.header}>製品リスト</Text>
        {DATA.map(item => (
          <View key={item.id} style={styles.item}>
            <Text style={styles.title}>{item.title}</Text>
          </View>
        ))}
        <Text style={styles.footer}>お問い合わせはサポートまで</Text>
      </ScrollView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 20,
  },
  scrollView: {
    marginHorizontal: 20,
  },
  header: {
    fontSize: 24,
    fontWeight: 'bold',
    marginTop: 10,
    marginBottom: 5,
  },
  infoText: {
    fontSize: 16,
    marginBottom: 15,
  },
  item: {
    backgroundColor: '#add8e6',
    padding: 20,
    marginVertical: 8,
    borderRadius: 5,
  },
  title: {
    fontSize: 18,
  },
  footer: {
    fontSize: 14,
    color: '#888',
    textAlign: 'center',
    marginTop: 20,
    marginBottom: 50,
  },
});

export default ScrollViewExample;

SectionList を使用する

SectionListは、データをセクション(見出し付きのグループ)に分けて表示したい場合に最適なコンポーネントです。

FlatListとの違い

  • Sticky Headers (固定ヘッダー): SectionListは、スクロールしてもセクションヘッダーが画面上部に固定される「Sticky Headers」機能をネイティブでサポートしています。
  • セクションの概念: FlatListは単一の配列を扱いますが、SectionListsectionsというプロパティを使用し、各セクションがtitle(またはrenderSectionHeader)とdata(そのセクション内のアイテムの配列)を持つオブジェクトの配列を期待します。

どのような場合に使うか

  • セクションヘッダーをスクロール時に固定したい場合:
  • セクションごとに見出しを付けたい場合:
  • データをカテゴリ分けして表示したい場合: アルファベット順の連絡先リスト、日付ごとのイベントリストなど。

コード例

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

const SECTIONS = [
  {
    title: 'フルーツ',
    data: [
      { id: 'f1', name: 'りんご' },
      { id: 'f2', name: 'バナナ' },
      { id: 'f3', name: 'みかん' },
    ],
  },
  {
    title: '野菜',
    data: [
      { id: 'v1', name: 'トマト' },
      { id: 'v2', name: 'きゅうり' },
      { id: 'v3', name: 'キャベツ' },
    ],
  },
  {
    title: '飲み物',
    data: [
      { id: 'd1', name: '水' },
      { id: 'd2', name: 'ジュース' },
      { id: 'd3', name: 'コーヒー' },
    ],
  },
];

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

  const renderSectionHeader = ({ section: { title } }) => (
    <Text style={styles.header}>{title}</Text>
  );

  return (
    <SafeAreaView style={styles.container}>
      <SectionList
        sections={SECTIONS} // セクションデータ
        keyExtractor={(item, index) => item.id + index}
        renderItem={renderItem}
        renderSectionHeader={renderSectionHeader}
        stickySectionHeadersEnabled // Sticky Headersを有効にする
      />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 20,
  },
  header: {
    fontSize: 28,
    backgroundColor: '#fff',
    paddingHorizontal: 16,
    paddingVertical: 8,
    fontWeight: 'bold',
  },
  item: {
    backgroundColor: '#f9c2ff',
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16,
  },
  title: {
    fontSize: 24,
  },
});

export default SectionListExample;

サードパーティ製のリストライブラリ (FlashListなど)

FlatListは多くのシナリオで非常に優れていますが、超大規模なデータセットや非常に複雑なアイテムのレンダリング、または可変サイズのアイテムで最高のパフォーマンスを得たい場合に、コミュニティが提供するより高度なライブラリを検討する価値があります。

最も注目すべき代替手段の一つは、Shopifyによって開発されたです](https://www.google.com/search?q=https://shopify.github.io/flash-list/)%E3%81%A7%E3%81%99)。

FlatListとの違い

  • APIの互換性: FlatListとほぼ同じAPIを持っているため、既存のFlatListコンポーネントをFlashListに置き換えるのが比較的容易です。
  • デフォルトでより良いパフォーマンス: 多くのケースで、特別な設定なしにFlatListよりも優れた初期パフォーマンスを提供します。
  • メモリ使用量の削減: FlatListよりもメモリ効率が良いとされています。
  • 高度な仮想化アルゴリズム: FlatListよりもさらに効率的な仮想化アルゴリズムを使用しており、特にアイテム数が非常に多い場合(数千、数万など)や、可変サイズのアイテムが多い場合に優れたパフォーマンスを発揮します。

どのような場合に使うか

  • FlatListでパフォーマンスのボトルネックに直面している場合: FlatListの最適化では限界があると感じる場合に。
  • 複雑なアイテムや可変サイズのアイテムが多いリスト: アイテムの高さが異なる場合でも高いパフォーマンスを維持したい場合。
  • 極めて大規模なリスト: ユーザーが無限にスクロールするようなフィードなど。

コード例 (インストールと使用)

まず、パッケージをインストールします。

npm install @shopify/flash-list
# または
yarn add @shopify/flash-list

次に、コード内でFlatListFlashListに置き換えることができます。

import React from 'react';
import { StyleSheet, Text, View, SafeAreaView } from 'react-native';
import { FlashList } from "@shopify/flash-list"; // FlashListをインポート

const DATA = Array.from({ length: 5000 }, (_, i) => ({
  id: String(i + 1),
  title: `超大量アイテム ${i + 1}`,
  // 高さのバリエーションを付けるためにランダムな高さを追加
  height: 50 + Math.random() * 50, // 50から100のランダムな高さ
}));

const FlashListExample = () => {
  const renderItem = ({ item }) => (
    <View style={[styles.item, { height: item.height }]}>
      <Text style={styles.title}>{item.title}</Text>
    </View>
  );

  return (
    <SafeAreaView style={styles.container}>
      <FlashList
        data={DATA}
        renderItem={renderItem}
        estimatedItemSize={75} // 可変高さのアイテムでは推定サイズが重要
        keyExtractor={item => item.id}
        // FlatListのほとんどのPropsが利用可能
      />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 20,
  },
  item: {
    backgroundColor: '#e6f7ff',
    padding: 10,
    marginVertical: 4,
    marginHorizontal: 16,
    borderRadius: 5,
    justifyContent: 'center',
    alignItems: 'center',
  },
  title: {
    fontSize: 16,
  },
});

export default FlashListExample;

ポイント

  • estimatedItemSize: FlashListでは、可変高さのアイテムを扱う場合にestimatedItemSizeプロパティを設定することが強く推奨されます。これにより、初期レンダリングやスクロールパフォーマンスが大幅に向上します。
  • FlashList: 大規模なリスト、特に可変高さのアイテムが多い場合や、FlatListではパフォーマンスが不足している場合に。
  • SectionList: データをセクションごとにグループ化して表示し、固定ヘッダーが必要な場合に。
  • ScrollView: アイテム数が少なく、多様なコンテンツをシンプルにスクロールさせたい場合に。

FlatListは「仮想化(Virtualization)」という仕組みを使って、大量のデータを効率的にレンダリングします。これは、画面に表示されているアイテムとその周辺のアイテムのみを実際に描画し、画面外のアイテムは描画しない(またはリサイクルする)ことで、メモリ使用量とパフォーマンスを最適化する技術です。

しかし、すべてのケースでFlatListが最適というわけではありません。以下に主な代替手段とその使い分けを説明します。

ScrollView

ScrollViewは、React Nativeでスクロール可能なコンテンツを作成するための最も基本的なコンポーネントです。

特徴

  • 非仮想化: FlatListのような仮想化の機能はありません。
  • 全コンポーネントのレンダリング: ScrollViewは、その中に含まれる全ての子コンポーネントを一度にレンダリングします。これは、リストのアイテム数が少ない場合に問題ありませんが、大量のアイテムがある場合はパフォーマンスの問題(メモリ消費の増大、UIの遅延)を引き起こす可能性があります。
  • シンプルさ: 非常にシンプルなAPIで、任意のコンポーネントをその中に配置し、スクロール可能にすることができます。

FlatListの代替としての使い分け

  • アイテムが頻繁に更新されないリスト: 頻繁なアイテムの追加・削除・並べ替えがない場合に適しています。
  • 異なる種類のコンテンツが混在するスクロールビュー: リスト形式のアイテムだけでなく、画像、テキストブロック、フォーム要素など、様々な種類のコンポーネントが混在するスクロールビューを作成する場合に適しています。例えば、ブログ記事の詳細画面や設定画面など。
  • アイテム数が非常に少ないリスト: 数個から数十個程度のアイテムで、スクロールが発生する程度の短いリストであれば、ScrollViewで十分な場合があります。

コード例

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

const SmallData = [
  { id: 'a', text: 'アイテムA' },
  { id: 'b', text: 'アイテムB' },
  { id: 'c', text: 'アイテムC' },
  // ... 数が少ないアイテム
];

const ScrollViewExample = () => {
  return (
    <SafeAreaView style={styles.container}>
      <ScrollView contentContainerStyle={styles.scrollViewContent}>
        <Text style={styles.header}>ScrollViewを使用したリスト</Text>
        {SmallData.map(item => (
          <View key={item.id} style={styles.item}>
            <Text style={styles.title}>{item.text}</Text>
          </View>
        ))}
        <Text style={styles.footer}>--- リストの終わり ---</Text>
      </ScrollView>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 20,
  },
  scrollViewContent: {
    paddingVertical: 10,
  },
  header: {
    fontSize: 24,
    fontWeight: 'bold',
    textAlign: 'center',
    marginBottom: 10,
  },
  item: {
    backgroundColor: '#add8e6',
    padding: 15,
    marginVertical: 5,
    marginHorizontal: 20,
    borderRadius: 8,
  },
  title: {
    fontSize: 18,
  },
  footer: {
    fontSize: 18,
    textAlign: 'center',
    marginTop: 20,
  },
});

export default ScrollViewExample;

SectionList

SectionListは、FlatListと同様に仮想化をサポートしますが、データをセクション(グループ)に分けて表示することに特化しています。各セクションには独自のヘッダーを持つことができます。

特徴

  • データ構造: dataプロパティは、セクションオブジェクトの配列を受け取ります。各セクションオブジェクトはtitle(セクションヘッダーの表示に利用)とdata(そのセクションのアイテム配列)を持ちます。
  • スティッキーヘッダー: 各セクションのヘッダーをスクロールしても画面上部に固定表示する機能(スティッキーヘッダー)をサポートします。
  • セクション表示: データがカテゴリやグループに分類されている場合に最適です。

FlatListの代替としての使い分け

  • FlatListではセクションヘッダーを実現するために複雑なロジックが必要になる場合があるため、そのような場合はSectionListを使う方が簡潔です。
  • セクション分けされたリスト: 電話帳、設定メニュー、日付ごとのイベントリストなど、データを論理的なグループに分けて表示したい場合に最適です。

コード例

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

const SECTIONS = [
  {
    title: 'A',
    data: [{ id: 'a1', value: 'Alice' }, { id: 'a2', value: 'Anna' }],
  },
  {
    title: 'B',
    data: [{ id: 'b1', value: 'Bob' }, { id: 'b2', value: 'Betty' }],
  },
  {
    title: 'C',
    data: [{ id: 'c1', value: 'Charlie' }, { id: 'c2', value: 'Catherine' }],
  },
];

const SectionListExample = () => {
  return (
    <SafeAreaView style={styles.container}>
      <SectionList
        sections={SECTIONS} // FlatListdataの代わりにsectionsを使う
        keyExtractor={(item, index) => item.id + index}
        renderItem={({ item }) => (
          <View style={styles.item}>
            <Text style={styles.title}>{item.value}</Text>
          </View>
        )}
        renderSectionHeader={({ section: { title } }) => (
          <Text style={styles.header}>{title}</Text>
        )}
        stickySectionHeadersEnabled={true} // ヘッダーをスティッキーにする
      />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 20,
  },
  header: {
    fontSize: 28,
    backgroundColor: '#fff',
    paddingHorizontal: 16,
    paddingVertical: 8,
    fontWeight: 'bold',
  },
  item: {
    backgroundColor: '#e0f2f7',
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16,
    borderRadius: 5,
  },
  title: {
    fontSize: 18,
  },
});

export default SectionListExample;

VirtualizedList (高度な用途)

FlatListSectionListの基盤となっているのがVirtualizedListです。これは、仮想化リストの最も低レベルな実装を提供します。

特徴

  • 仮想化: FlatListと同様に仮想化をサポートし、パフォーマンスを最適化します。
  • より複雑なAPI: FlatListに比べて設定するプロパティが多く、より複雑です。例えば、dataの他にgetItemgetItemCountなどのプロパティを自分で実装する必要があります。
  • 究極の柔軟性: FlatListSectionListでは対応できない、非常に特殊なデータ構造やレンダリングロジックが必要な場合に利用します。

FlatListの代替としての使い分け

  • 上級者向け: 通常のアプリケーション開発ではほとんど使うことはなく、FlatListSectionListで十分な場合が多いです。
  • 独自の仮想化ロジックが必要な場合: 例えば、イミュータブルなデータ構造を直接扱う、非常に複雑なグリッドレイアウトをレンダリングするなど、FlatListのAPIでは対応できないケース。

コード例(概念のみ - 実用的な例はより複雑になります)

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

const MY_RAW_DATA = [
  { id: 'v1', text: '仮想化アイテム1' },
  { id: 'v2', text: '仮想化アイテム2' },
  // ... 大量のデータ
];

const VirtualizedListExample = () => {
  const getItem = (data, index) => data[index];
  const getItemCount = (data) => data.length;

  return (
    <SafeAreaView style={styles.container}>
      <VirtualizedList
        data={MY_RAW_DATA} // これは FlatListdata とは異なり、getItem/getItemCount でアクセスされる
        renderItem={({ item }) => (
          <View style={styles.item}>
            <Text style={styles.title}>{item.text}</Text>
          </View>
        )}
        keyExtractor={item => item.id}
        getItem={getItem} // 各アイテムを取得する関数
        getItemCount={getItemCount} // アイテムの総数を返す関数
        initialNumToRender={10}
      />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 20,
  },
  item: {
    backgroundColor: '#b0e0e6',
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16,
    borderRadius: 5,
  },
  title: {
    fontSize: 18,
  },
});

export default VirtualizedListExample;

サードパーティ製のリストコンポーネント

React Nativeコミュニティでは、FlatListのパフォーマンスをさらに改善したり、特定のユースケースに対応したりするためのサードパーティ製ライブラリが多数開発されています。

主なもの

  • recyclerlistview: Flipkartが開発した、非常に高性能なリストビュー。FlashListと同様にビューのリサイクルを行います。APIがFlatListとはやや異なりますが、複雑なレイアウトや大規模なリストで真価を発揮します。
  • @shopify/flash-list: Shopifyが開発した高性能なリストコンポーネントで、FlatListとほぼ同じAPIを持ちながら、より高いパフォーマンス(特にAndroidデバイスや複雑なアイテムで顕著)を実現します。内部的には「ビューのリサイクル」という技術を使用しています。現在、最も推奨されるFlatListの代替候補の一つです。

FlatListの代替としての使い分け

  • RecyclerListView: より深いカスタマイズとパフォーマンスが求められるが、学習コストがかかる。
  • FlashList: ほとんどのケースでFlatListから簡単に置き換え可能で、高いパフォーマンス改善が期待できます。
  • FlatListのAPIで対応しきれない複雑な要件: 例えば、アイテムの高さが非常に多様でgetItemLayoutでは対応しきれない、などの場合。
  • 究極のパフォーマンスが求められる場合: FlatListでパフォーマンスのボトルネックに直面している場合。
import React from 'react';
import { StyleSheet, Text, View, SafeAreaView } from 'react-native';
import { FlashList } from "@shopify/flash-list"; // FlashListをインポート

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

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

  return (
    <SafeAreaView style={styles.container}>
      <FlashList
        data={LARGE_DATA} // FlatList と同じく data プロパティを使用
        renderItem={renderItem}
        estimatedItemSize={60} // FlashList ではパフォーマンス向上のためにこれを設定することが非常に重要
        keyExtractor={item => item.id}
      />
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: 20,
  },
  item: {
    backgroundColor: '#ffdbed',
    padding: 20,
    marginVertical: 4,
    marginHorizontal: 16,
    borderRadius: 5,
  },
  title: {
    fontSize: 18,
  },
});

export default FlashListExample;
  • 非常に特殊な要件や究極のパフォーマンス: VirtualizedList (低レベルAPI) またはサードパーティ製の高性能リスト (@shopify/flash-list, recyclerlistview)
  • セクション分けされた長いリスト: SectionList
  • 一般的な長いリスト(セクションなし): FlatList (またはより高性能なFlashList)
  • 簡単な短いリスト: ScrollView