FlatList#data
FlatList#data
とは
FlatList
は、大量のデータを効率的に表示するためのReact Nativeのコンポーネントです。特に、スクロール可能なリスト表示に適しています。このFlatList
を使用する際に、表示したいデータの集合(リスト) を指定するのが、data
プロパティの役割です。
役割と重要性
data
プロパティは、FlatList
にとって必須のプロパティです。これがないと、FlatList
は何を表示すればよいかわかりません。
具体的には、以下のような特徴があります。
-
表示するデータの提供:
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} />
-
パフォーマンスの最適化:
FlatList
は、data
プロパティに渡された配列全体を一度に描画するわけではありません。代わりに、画面に表示されている(または表示されそうな)アイテムだけをレンダリングすることで、メモリ使用量を抑え、パフォーマンスを向上させます。これを「仮想化 (Virtualization)」と呼びます。 -
データの更新と再レンダリング:
data
プロパティに渡す配列が更新されると、FlatList
は自動的に変更を検知し、リストを再レンダリングします。これにより、リアルタイムなデータの追加、削除、変更がスムーズに反映されます。
- 各アイテムの一意なキー:
FlatList
は、リスト内の各アイテムを一意に識別するためにkeyExtractor
プロパティを必要とします。通常、data
配列内の各オブジェクトには、id
やkey
といった一意な識別子を含めることが推奨されます。もしデータに一意なキーがない場合でも、keyExtractor
を使ってインデックスなどから一意なキーを生成する必要があります。
データが表示されない、または一部しか表示されない
よくある原因
- データがState/Propsの更新時に変更されていない: Reactのコンポーネントは、StateやPropsが「変更された」と判断しないと再レンダリングされません。もし
data
配列の中身だけが変わって、配列そのものの参照が変わっていない場合、FlatList
は更新を検知できません。 - スタイルやレイアウトの問題:
FlatList
やその親コンポーネントのスタイル(特にflex
プロパティなど)が原因で、リストが描画されていない場合があります。 keyExtractor
が正しく定義されていない、または一意でない:FlatList
は各アイテムを一意に識別するためにkeyExtractor
を必要とします。これが欠けているか、一意でないキーを返していると、リストの更新時に問題が発生したり、警告が表示されたりします。renderItem
が正しく定義されていない:data
に渡された各アイテムをどのようにレンダリングするかを定義するrenderItem
プロパティが正しく設定されていない場合、何も表示されません。data
が配列ではない:FlatList
のdata
プロパティは必ず配列である必要があります。オブジェクトや他のデータ型を渡すと表示されません。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: 1
やheight
などを明示的に指定して、表示領域を確保します。
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がリストアイテムの変更を正しく追跡できず、不要な再レンダリングが発生します。
トラブルシューティング
initialNumToRender
やmaxToRenderPerBatch
などの調整: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更新のバッチ処理や、必要な時のみデータを更新するロジックを検討します。
useMemo
やuseCallback
フックを使って、data
やrenderItem
などの参照が変わらないようにします。
renderItem
の最適化:renderItem
でレンダリングされるアイテムコンポーネントをReact.memo
でラップするか、PureComponent
として定義して、Propsが変更されない限り再レンダリングされないようにします。renderItem
内で複雑なロジックや重い処理を行わないようにします。- 画像を使用する場合は、
react-native-fast-image
のような画像をキャッシュするライブラリの使用を検討します。
keyExtractor
を最適化する:- 最も重要な点は、各アイテムが持つ一意のIDを
keyExtractor
で返すことです。 - 常に同じキーを返している、またはランダムな値をキーにしている場合は修正が必要です。
- 最も重要な点は、各アイテムが持つ一意のIDを
データが更新されてもFlatListが再レンダリングされない
よくある原因
extraData
の欠如:FlatList
のrenderItem
関数内で使用されているデータが、data
プロパティに含まれるアイテム自体ではなく、親コンポーネントのStateや別のPropsに依存している場合、FlatList
はその変更を自動的に検知できません。data
配列の参照が変わっていない: Reactは、PropsやStateが浅い比較(shallow comparison)によって変更されたと判断した場合にのみコンポーネントを再レンダリングします。配列の中身だけが変わっても、配列オブジェクトそのものの参照が変わっていないと、FlatList
は更新を検知しません。
extraData
の使用:FlatList
のrenderItem
で、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
:toggleComplete
やdeleteItem
、renderItem
のようなコールバック関数をuseCallback
でメモ化することで、親コンポーネントが再レンダリングされてもこれらの関数の参照が変わらないようにできます。これにより、FlatList
の不要な再レンダリングを防ぎ、パフォーマンスを向上させることができます。特にrenderItem
は頻繁に呼び出される可能性があるため、メモ化が推奨されます。map
とfilter
: アイテムの更新には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;
initialNumToRender
、windowSize
: 大量のデータを扱う際に、これらのプロパティを調整することで、初期レンダリング数やメモリ使用量を制御し、パフォーマンスをさらに最適化できます。extraData
:data
プロパティが変更されていないが、FlatList
のアイテム表示に影響を与える可能性のある別のState(この例ではselectedId
)がある場合に設定します。extraData
の値が変更されると、FlatList
は再レンダリングを検討します。getItemLayout
:data
とindex
を受け取り、{ length: itemHeight, offset: itemOffset, index: index }
の形式のオブジェクトを返します。これによりFlatList
は各アイテムの正確な位置を事前に計算でき、スクロール時の空白表示(ブランクスペース)を防ぎ、スムーズなスクロールを実現します。
FlatList#data
の代替プログラミング方法
FlatList
は、data
プロパティに渡された単一の配列から効率的なリストを生成します。代替手段は、この「単一の配列」という制約や、FlatList
が提供する特定の最適化が不要、または他の機能が必要な場合に考慮されます。
ScrollView を使用する
ScrollView
は、すべての内容を一度にレンダリングするシンプルなスクロールコンテナです。
FlatListとの違い
- シンプルな使用:
FlatList
のようにdata
プロパティやrenderItem
、keyExtractor
といった特定のプロパティを設定する必要がなく、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
は単一の配列を扱いますが、SectionList
はsections
というプロパティを使用し、各セクションが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
次に、コード内でFlatList
をFlashList
に置き換えることができます。
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} // FlatListのdataの代わりに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 (高度な用途)
FlatList
とSectionList
の基盤となっているのがVirtualizedList
です。これは、仮想化リストの最も低レベルな実装を提供します。
特徴
- 仮想化:
FlatList
と同様に仮想化をサポートし、パフォーマンスを最適化します。 - より複雑なAPI:
FlatList
に比べて設定するプロパティが多く、より複雑です。例えば、data
の他にgetItem
、getItemCount
などのプロパティを自分で実装する必要があります。 - 究極の柔軟性:
FlatList
やSectionList
では対応できない、非常に特殊なデータ構造やレンダリングロジックが必要な場合に利用します。
FlatListの代替としての使い分け
- 上級者向け: 通常のアプリケーション開発ではほとんど使うことはなく、
FlatList
やSectionList
で十分な場合が多いです。 - 独自の仮想化ロジックが必要な場合: 例えば、イミュータブルなデータ構造を直接扱う、非常に複雑なグリッドレイアウトをレンダリングするなど、
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} // これは FlatList の data とは異なり、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