FlatList#extraData
By passing this additional data to the extraData
prop, you inform FlatList
that it should reconsider rendering its items, even if the data
prop itself hasn't changed. This is crucial for ensuring your UI updates correctly when non-data-array-related state changes impact item rendering.
Imagine you have a FlatList
displaying a list of items, and you want to highlight a selected item.
Your data
prop would contain the list of items.
Your extraData
prop could contain the selectedItemId
.
When selectedItemId
changes, even if the data
array remains the same, FlatList
will re-render to reflect the new highlighted item, because you've told it that extraData
has changed. Without extraData
, the FlatList
might not update the highlighting as it wouldn't detect a change in its primary data
prop.
リストアイテムが再レンダリングされない
問題
data
プロパティのデータ自体は変わっていないが、renderItem
の内部で利用している別の状態(例: 選択中のアイテムID、テーマ設定など)が変更されても、リストアイテムのUIが更新されない。
原因
FlatList
は であるため、そのプロパティがシャロー比較(参照の比較)で変更がないと判断された場合、再レンダリングを行いません。data
プロパティの配列自体が変わっていなければ、FlatList
は変更を検知しないため、renderItem
内で使用されている別の状態が変化しても再レンダリングされません。
トラブルシューティング/解決策
extraData
プロパティに、renderItem
関数が依存するすべての追加データを渡します。これにより、extraData
の値が変更されたときに FlatList
が再レンダリングをトリガーするようになります。
例
const MyList = () => {
const [selectedId, setSelectedId] = useState(null);
const data = [/* ...アイテムの配列 */];
const renderItem = ({ item }) => {
const backgroundColor = item.id === selectedId ? '#6e3b6e' : '#f9c2ff';
return (
<Item
item={item}
onPress={() => setSelectedId(item.id)}
backgroundColor={backgroundColor}
/>
);
};
return (
<FlatList
data={data}
renderItem={renderItem}
keyExtractor={(item) => item.id.toString()}
// ここが重要: selectedId を extraData に渡す
extraData={selectedId}
/>
);
};
この例では、selectedId
が変更されるたびに FlatList
が再レンダリングを検知し、renderItem
が適切に実行されるため、選択されたアイテムの背景色が変わります。
extraData に何を渡すべきかわからない/間違った値を渡している
問題
extraData
に何を設定すればよいか混乱する、またはオブジェクト全体を渡してしまい、意図しない再レンダリングが発生する。
原因
extraData
には、data
以外の、リストアイテムのレンダリングに影響を与える変化する可能性のある値を渡すべきです。コンポーネントのステート全体や、不必要に大きなオブジェクトを渡すと、関連性のない変更でもリスト全体が再レンダリングされてしまい、パフォーマンスに悪影響を与える可能性があります。
トラブルシューティング/解決策
extraData
には、renderItem
関数内で使用され、data
プロパティに含まれていない、変更時にリストの再レンダリングを必要とする最小限の値を渡すようにします。
悪い例
// 全てのstateを渡すと、selectedId以外が変更されても再レンダリングされる
<FlatList data={data} extraData={this.state} renderItem={...} />
良い例
// selectedId のみに依存する場合
<FlatList data={data} extraData={selectedId} renderItem={...} />
// 複数の依存関係がある場合 (オブジェクトでまとめて渡す)
<FlatList data={data} extraData={{ selectedId, theme }} renderItem={...} />
このように、変更を検知したい特定のステート変数やプロパティのみを渡すことで、無駄な再レンダリングを防ぎ、パフォーマンスを最適化できます。オブジェクトを渡す場合は、そのオブジェクト自体が新しい参照になるように(例えば useState
や useReducer
で正しく更新されるように)注意が必要です。
不必要な再レンダリングによるパフォーマンスの低下
問題
extraData
を使用しているにもかかわらず、リストのスクロールがカクついたり、パフォーマンスが低い。
原因
extraData
に渡す値が頻繁に、かつ不必要に更新されている可能性があります。extraData
は FlatList
のシャロー比較の対象となるため、値が少しでも変わると FlatList
全体が再レンダリングを考慮します。renderItem
が重い処理を行っている場合、これがパフォーマンスの問題に直結します。
トラブルシューティング/解決策
- renderItem の最適化
renderItem
関数内で重い計算を避ける、またはReact.memo
を使用して個々のリストアイテムコンポーネントをメモ化することを検討します。const MyListItem = React.memo(({ item, selectedId, onPress }) => { const backgroundColor = item.id === selectedId ? '#6e3b6e' : '#f9c2ff'; return ( <TouchableOpacity onPress={onPress} style={{ backgroundColor }}> <Text>{item.title}</Text> </TouchableOpacity> ); }); const renderItem = ({ item }) => ( <MyListItem item={item} selectedId={selectedId} onPress={() => setSelectedId(item.id)} /> );
React.memo
を使用することで、MyListItem
コンポーネントは、item
,selectedId
,onPress
のいずれかのプロップが変更された場合にのみ再レンダリングされるようになります。 - extraData の値をメモ化する
useMemo
フックなどを使用して、extraData
に渡すオブジェクトや値が不要な場合に新しい参照を作成しないようにします。const extraDataToPass = useMemo(() => ({ selectedId, filterCriteria }), [selectedId, filterCriteria]); return ( <FlatList data={data} renderItem={renderItem} keyExtractor={(item) => item.id.toString()} extraData={extraDataToPass} /> );
- extraData を必要最小限に留める
上記の2番目の項目で説明したように、本当に必要なデータのみを渡します。
extraData を設定しても期待通りに動作しない(Hooks環境の場合)
問題
useState
や useReducer
を使用している場合でも、extraData
がうまく機能しないように見える。
原因
Hooks の使い方自体に問題があるか、extraData
に渡される値が実際に変更されていないにもかかわらず、変更されたと思い込んでいる場合があります。
トラブルシューティング/解決策
- オブジェクトの参照
extraData
にオブジェクトを渡している場合、そのオブジェクトの参照が毎回新しいものになっていることを確認します。例えば、extraData={{ count: myCount }}
のように直接オブジェクトリテラルを渡すと、myCount
が変わるたびに新しいオブジェクトが作成されるため、FlatList
は変更を検知します。しかし、extraData={myObject}
のように、myObject
のプロパティが変わってもmyObject
自体の参照が変わらない場合、FlatList
は変更を検知しません。この場合はuseMemo
が有効です。 - ステートの更新を確認
useState
のセッター関数が正しく呼ばれ、ステートが実際に更新されているか確認します。
FlatList#extraData
は、data
プロパティに含まれない状態の変更によってリストアイテムのUIを更新する必要がある場合に非常に便利ですが、その動作原理(シャロー比較)を理解し、適切に使用することが重要です。
- パフォーマンス
不必要な再レンダリングを防ぐために、extraData
に渡す値を適切に管理し、renderItem
やリストアイテムコンポーネント自体を最適化する。 - 何を渡すか
renderItem
が依存する、data
以外の、変更時に再レンダリングをトリガーしたい最小限の値(プリミティブ値、または新しい参照を持つオブジェクト)。 - いつ使うか
FlatList
のdata
プロパティの配列自体は変更されないが、リストアイテムの見た目や挙動に影響を与える別の状態が変更された場合。
FlatList#extraData
は、FlatList
が data
プロパティの配列以外の変更を検知し、リストアイテムの再レンダリングをトリガーするために使用されます。これにより、リスト内の個々のアイテムの表示が、data
に含まれない状態に基づいて変化する場合に、正しくUIが更新されるようになります。
例1: 選択されたアイテムのハイライト表示
最も一般的なユースケースの一つです。リスト内のアイテムが選択されたときに、そのアイテムの背景色を変えるなどしてハイライト表示するケースです。
import React, { useState, useCallback } from 'react';
import { FlatList, Text, TouchableOpacity, StyleSheet, View } from 'react-native';
const DATA = [
{ id: '1', title: '最初のアイテム' },
{ id: '2', title: '二番目のアイテム' },
{ id: '3', title: '三番目のアイテム' },
{ id: '4', title: '四番目のアイテム' },
{ id: '5', title: '五番目のアイテム' },
];
const Item = React.memo(({ item, onPress, backgroundColor, textColor }) => {
console.log(`アイテム ${item.title} がレンダリングされました`); // 再レンダリング確認用
return (
<TouchableOpacity onPress={onPress} style={[styles.item, { backgroundColor }]}>
<Text style={[styles.title, { color: textColor }]}>{item.title}</Text>
</TouchableOpacity>
);
});
const MyHighlightableList = () => {
const [selectedId, setSelectedId] = useState(null); // 選択されたアイテムのIDを保持
// renderItem 関数は FlatList の外で定義するか、useCallback でメモ化すると良い
// selectedId の変更に依存するため、selectedId が変わったらこの関数も再生成されるようにする
const renderItem = useCallback(
({ item }) => {
const backgroundColor = item.id === selectedId ? '#6e3b6e' : '#f9c2ff';
const textColor = item.id === selectedId ? 'white' : 'black';
return (
<Item
item={item}
onPress={() => setSelectedId(item.id)}
backgroundColor={backgroundColor}
textColor={textColor}
/>
);
},
[selectedId] // selectedId が変更された場合にのみ renderItem を再生成
);
return (
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={(item) => item.id}
// ここが FlatList#extraData のキモ!
// selectedId が変更されるたびに FlatList に再レンダリングを促す
extraData={selectedId}
ListHeaderComponent={() => (
<Text style={styles.header}>選択可能なリスト</Text>
)}
/>
);
};
const styles = StyleSheet.create({
header: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginVertical: 10,
},
item: {
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
borderRadius: 8,
},
title: {
fontSize: 18,
},
});
export default MyHighlightableList;
解説
- selectedId ステート
どのアイテムが現在選択されているかを管理するためにselectedId
というステートを使用しています。 - Item コンポーネントのメモ化
React.memo
を使用してItem
コンポーネントをメモ化しています。これにより、item
,onPress
,backgroundColor
,textColor
のプロップが変更されない限り、Item
コンポーネントは再レンダリングされません。 - renderItem の useCallback
renderItem
関数自体もuseCallback
でメモ化しています。これにより、FlatList
が不要な再レンダリングを避けることができます。renderItem
はselectedId
に依存するため、依存配列にselectedId
を含めています。 - extraData={selectedId}
これが最も重要な部分です。selectedId
はdata
プロパティの配列の一部ではありませんが、リストアイテムの見た目(背景色)に影響を与えます。selectedId
の値が変更されると、FlatList
はextraData
の変更を検知します。FlatList
はその変更を受けて、renderItem
関数を再実行し、リストアイテムを再レンダリングすべきかどうかを判断します。renderItem
内ではselectedId
を参照してbackgroundColor
を計算しているため、該当するアイテムのみが新しい背景色で再レンダリングされます。- もし
extraData={selectedId}
がない場合、selectedId
が変更されてもFlatList
はその変更を検知せず、リストアイテムのUIは更新されないままになります。
例2: グローバルなフィルター状態によるアイテムの表示/非表示
リスト全体の表示を制御するフィルターのような、data
とは直接関係ないグローバルな状態がある場合です。
import React, { useState, useCallback, useMemo } from 'react';
import { FlatList, Text, TouchableOpacity, StyleSheet, View, Switch } from 'react-native';
const ALL_PRODUCTS = [
{ id: 'a1', name: 'りんご', category: 'フルーツ', inStock: true },
{ id: 'b2', name: 'バナナ', category: 'フルーツ', inStock: false },
{ id: 'c3', name: 'トマト', category: '野菜', inStock: true },
{ id: 'd4', name: '牛乳', category: '乳製品', inStock: true },
{ id: 'e5', name: 'パン', category: '主食', inStock: false },
];
const ProductItem = React.memo(({ product }) => {
console.log(`プロダクト ${product.name} がレンダリングされました`);
return (
<View style={styles.productItem}>
<Text style={styles.productName}>{product.name}</Text>
<Text style={styles.productCategory}>{product.category}</Text>
<Text style={[styles.productStock, { color: product.inStock ? 'green' : 'red' }]}>
{product.inStock ? '在庫あり' : '在庫なし'}
</Text>
</View>
);
});
const MyFilterableList = () => {
const [showInStockOnly, setShowInStockOnly] = useState(false); // 在庫のある商品のみ表示するかどうか
// フィルターされたデータを計算する
const filteredProducts = useMemo(() => {
if (showInStockOnly) {
return ALL_PRODUCTS.filter(product => product.inStock);
}
return ALL_PRODUCTS;
}, [showInStockOnly]); // showInStockOnly が変更された場合にのみ再計算
const renderProductItem = useCallback(({ item }) => (
<ProductItem product={item} />
), []); // この renderItem は item のみに依存し、その他は prop で渡されないため、空の依存配列でOK
return (
<View style={{ flex: 1 }}>
<View style={styles.filterContainer}>
<Text style={styles.filterText}>在庫ありのみ表示:</Text>
<Switch
onValueChange={setShowInStockOnly}
value={showInStockOnly}
/>
</View>
<FlatList
data={filteredProducts} // ここで filterProducts を渡す
renderItem={renderProductItem}
keyExtractor={(item) => item.id}
// extraData はここでは不要です!
// なぜなら、showInStockOnly が変わると filteredProducts 自体が新しい配列になるため
// FlatList は data プロパティの変更を自動的に検知します。
ListHeaderComponent={() => (
<Text style={styles.header}>商品リスト</Text>
)}
/>
</View>
);
};
const styles = StyleSheet.create({
header: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginVertical: 10,
},
filterContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
padding: 10,
backgroundColor: '#eee',
marginVertical: 5,
},
filterText: {
marginRight: 10,
fontSize: 16,
},
productItem: {
padding: 15,
marginVertical: 5,
marginHorizontal: 16,
backgroundColor: '#fff',
borderRadius: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.2,
shadowRadius: 1.41,
elevation: 2,
},
productName: {
fontSize: 18,
fontWeight: 'bold',
},
productCategory: {
fontSize: 14,
color: '#666',
},
productStock: {
fontSize: 14,
fontWeight: 'bold',
},
});
export default MyFilterableList;
解説
extraData
は、data
プロパティの配列そのものに影響を与えないが、リストアイテムのレンダリングに影響を与える外部の状態がある場合に主に利用されます。このようにdata
プロパティ自体が変更される場合は必要ありません。FlatList
はdata
プロパティの変更(ここでは新しい配列の参照)を自動的に検知し、適切に再レンダリングを行います。- なぜなら、
showInStockOnly
の値が変更されると、useMemo
フックによってfilteredProducts
という新しい配列が生成されます。 - この例では、
extraData
は不要です。
例3: テーマの切り替えや表示モードの変更
アプリ全体に影響する設定(ダークモード/ライトモード、コンパクト表示/詳細表示など)がリストアイテムの見た目に影響する場合です。
import React, { useState, useCallback } from 'react';
import { FlatList, Text, TouchableOpacity, StyleSheet, View, Switch } from 'react-native';
const DATA = [
{ id: '101', name: 'レポートA', type: 'document' },
{ id: '102', name: '写真B', type: 'image' },
{ id: '103', name: '音声C', type: 'audio' },
{ id: '104', name: 'ビデオD', type: 'video' },
];
const DarkModeItem = React.memo(({ item, isDarkMode }) => {
console.log(`アイテム ${item.name} がレンダリングされました (ダークモード: ${isDarkMode})`);
const itemStyle = isDarkMode ? styles.darkItem : styles.lightItem;
const textStyle = isDarkMode ? styles.darkText : styles.lightText;
return (
<View style={[styles.itemBase, itemStyle]}>
<Text style={textStyle}>{item.name}</Text>
<Text style={textStyle}>タイプ: {item.type}</Text>
</View>
);
});
const MyThemeableList = () => {
const [isDarkMode, setIsDarkMode] = useState(false); // ダークモードの状態
const renderItem = useCallback(
({ item }) => (
<DarkModeItem item={item} isDarkMode={isDarkMode} />
),
[isDarkMode] // isDarkMode が変更された場合にのみ renderItem を再生成
);
return (
<View style={{ flex: 1, backgroundColor: isDarkMode ? '#333' : '#f0f0f0' }}>
<View style={styles.themeToggleContainer}>
<Text style={styles.themeToggleText}>ダークモード:</Text>
<Switch
onValueChange={setIsDarkMode}
value={isDarkMode}
trackColor={{ false: "#767577", true: "#81b0ff" }}
thumbColor={isDarkMode ? "#f5dd4b" : "#f4f3f4"}
/>
</View>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={(item) => item.id}
// extraData に isDarkMode を渡す
// isDarkMode の変更を FlatList に伝えることで、すべてのアイテムが再レンダリングされ、
// 新しいテーマが適用される。
extraData={isDarkMode}
ListHeaderComponent={() => (
<Text style={[styles.header, { color: isDarkMode ? 'white' : 'black' }]}>
テーマ切り替えリスト
</Text>
)}
/>
</View>
);
};
const styles = StyleSheet.create({
header: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginVertical: 10,
},
themeToggleContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
padding: 10,
backgroundColor: '#ddd',
marginVertical: 5,
},
themeToggleText: {
marginRight: 10,
fontSize: 16,
},
itemBase: {
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
borderRadius: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.2,
shadowRadius: 1.41,
elevation: 2,
},
lightItem: {
backgroundColor: '#fff',
},
darkItem: {
backgroundColor: '#555',
},
lightText: {
color: 'black',
},
darkText: {
color: 'white',
},
});
export default MyThemeableList;
解説
- isDarkMode ステート
アプリ全体のテーマを制御するisDarkMode
ステートがあります。 - DarkModeItem のメモ化
DarkModeItem
はitem
とisDarkMode
のプロップに依存します。isDarkMode
が変わると、すべてのDarkModeItem
が再レンダリングされ、テーマが切り替わります。 - extraData={isDarkMode}
ここでisDarkMode
をextraData
に渡しています。isDarkMode
の値がtrue
とfalse
の間で切り替わるたびに、FlatList
はextraData
の変更を検知します。- これにより、
FlatList
はすべてのリストアイテムのrenderItem
を再度実行し、新しいテーマに応じたスタイルが適用されます。 FlatList
がisDarkMode
の変更を検知しなければ、リストアイテムの見た目は変わらないままになります。
これらの例からわかるように、FlatList#extraData
は主に以下のシナリオで役立ちます。
FlatList
がPureComponent
のように動作するため、プロパティのシャロー比較で変更を検知できない場合に、明示的に再レンダリングを促す必要がある。data
プロパティの配列自体は変更されないが、リストアイテムの見た目や動作に影響を与える外部の状態(選択状態、テーマ、表示モードなど)がある場合。
data プロパティの更新
最も直接的で、多くの場合に推奨される方法です。リストアイテムのレンダリングに影響を与える状態が変更された際に、FlatList
の data
プロパティ自体を新しい配列で更新します。
どのように機能するか
React の原則に従い、UIはステートから計算されます。FlatList
は data
プロパティに渡された配列の参照を監視しています。この参照が新しいものに変われば、FlatList
は新しいデータセットが渡されたと判断し、変更を検知してリストを再レンダリングします。
例
選択状態を持つアイテムのフィルタリング
(これは以前の例2と似ていますが、再確認のために掲載します)
import React, { useState, useCallback, useMemo } from 'react';
import { FlatList, Text, TouchableOpacity, StyleSheet, View } from 'react-native';
const INITIAL_DATA = [
{ id: '1', title: 'アイテムA', selected: false },
{ id: '2', title: 'アイテムB', selected: false },
{ id: '3', title: 'アイテムC', selected: false },
];
const MyItem = React.memo(({ item, onPress }) => {
console.log(`アイテム ${item.title} がレンダリングされました (data更新)`);
const backgroundColor = item.selected ? '#6e3b6e' : '#f9c2ff';
const textColor = item.selected ? 'white' : 'black';
return (
<TouchableOpacity onPress={onPress} style={[styles.item, { backgroundColor }]}>
<Text style={[styles.title, { color: textColor }]}>{item.title}</Text>
</TouchableOpacity>
);
});
const DataUpdateAlternative = () => {
const [items, setItems] = useState(INITIAL_DATA);
const handlePress = useCallback((id) => {
// 既存の配列を直接変更せず、新しい配列を作成してステートを更新する
setItems(prevItems =>
prevItems.map(item =>
item.id === id ? { ...item, selected: !item.selected } : item
)
);
}, []);
const renderItem = useCallback(
({ item }) => (
<MyItem
item={item}
onPress={() => handlePress(item.id)}
/>
),
[handlePress] // handlePress が変更されたら renderItem も再生成
);
return (
<FlatList
data={items} // ここで items ステート(新しい配列)を渡す
renderItem={renderItem}
keyExtractor={(item) => item.id}
// extraData は不要! data が更新されるたびに FlatList が再レンダリングされる
ListHeaderComponent={() => (
<Text style={styles.header}>data更新による代替</Text>
)}
/>
);
};
const styles = StyleSheet.create({
header: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginVertical: 10,
},
item: {
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
borderRadius: 8,
},
title: {
fontSize: 18,
},
});
export default DataUpdateAlternative;
メリット
- 信頼性
FlatList
はdata
プロパティの変更を最も効率的に処理するように設計されています。 - クリーン
extraData
のような追加のプロパティを管理する必要がありません。 - 直感的
React のデータフローに最も合致しています。
デメリット
renderItem
の内部でしか使われない一時的なUI状態(例:アイテム内のサブコンポーネントの開閉状態など)をdata
に含めるのは、データの純粋性を損なう可能性があります。data
配列の要素数が多い場合、毎回新しい配列を作成するオーバーヘッドが発生する可能性があります(ただし、通常はReactの仮想DOMと再調整アルゴリズムによって最適化されます)。
各アイテムコンポーネントでの状態管理と React.memo
各リストアイテムが自身に関連する特定のUI状態を管理し、その状態変更に応じて自身を再レンダリングするようにします。FlatList
には、アイテムのレンダリングに必要な最小限のデータのみを渡し、残りのUI状態は各アイテムコンポーネント内部で管理します。
どのように機能するか
FlatList
の renderItem
から渡される item
データは不変のままですが、個々のアイテムコンポーネントが自身の内部状態(例:選択されているかどうか、開いているかどうか)を持つことで、その状態変更によって自身のみを再レンダリングします。React.memo
を使用することで、FlatList
がアイテムを再レンダリングするたびに、プロップが変更されていないアイテムはスキップされます。
例
各アイテムが自身の選択状態を管理
import React, { useState, useCallback, memo } from 'react';
import { FlatList, Text, TouchableOpacity, StyleSheet, View } from 'react-native';
const DATA = [
{ id: '1', title: 'アイテムA' },
{ id: '2', title: 'アイテムB' },
{ id: '3', title: 'アイテムC' },
];
// 各アイテムコンポーネントが自身の選択状態を管理
const SelfContainedItem = memo(({ item }) => {
const [isSelected, setIsSelected] = useState(false); // 各アイテムが自身の選択状態を持つ
console.log(`アイテム ${item.title} がレンダリングされました (自己管理)`);
const handlePress = useCallback(() => {
setIsSelected(prev => !prev);
}, []);
const backgroundColor = isSelected ? '#6e3b6e' : '#f9c2ff';
const textColor = isSelected ? 'white' : 'black';
return (
<TouchableOpacity onPress={handlePress} style={[styles.item, { backgroundColor }]}>
<Text style={[styles.title, { color: textColor }]}>{item.title}</Text>
</TouchableOpacity>
);
});
const SelfContainedItemAlternative = () => {
const renderItem = useCallback(
({ item }) => (
<SelfContainedItem item={item} />
),
[] // SelfContainedItem は item prop のみに依存し、自身の状態を持つため、依存配列は空でOK
);
return (
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={(item) => item.id}
// extraData は不要! 各アイテムが自身で状態を持つため
ListHeaderComponent={() => (
<Text style={styles.header}>各アイテムでの状態管理</Text>
)}
/>
);
};
const styles = StyleSheet.create({
header: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginVertical: 10,
},
item: {
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
borderRadius: 8,
},
title: {
fontSize: 18,
},
});
export default SelfContainedItemAlternative;
メリット
- スケーラビリティ
リストが非常に長く、個々のアイテムの状態変化が頻繁に起こる場合に特に有効です。 - パフォーマンス
状態が変更されたアイテムのみが再レンダリングされ、他のアイテムは影響を受けません。FlatList
全体の再レンダリングを防げます。 - 責務の分離
各アイテムが自身のUI状態を管理するため、コードがモジュール化され、理解しやすくなります。
デメリット
- アイテム間で協調して動作する必要がある場合は、上位のコンポーネントで状態を管理し、
extraData
またはdata
の更新を用いる方が適切です。
Context API または状態管理ライブラリの使用
アプリケーション全体で共有される状態(例:現在のテーマ、ログインユーザー、選択中のアイテムのコレクションなど)を管理する場合、Context API や Redux、Zustand、MobX などの状態管理ライブラリを使用できます。
どのように機能するか
これらのシステムは、コンポーネントツリーのどこにいても状態にアクセスし、更新できるようにします。FlatList
の各アイテムは、直接 Context やストアから必要な状態(例:isDarkMode
、selectedItems
のセット)を購読し、その状態が変更された場合に自身を再レンダリングします。
例
ダークモードの切り替えをContext APIで管理
import React, { useState, useCallback, createContext, useContext, memo } from 'react';
import { FlatList, Text, TouchableOpacity, StyleSheet, View, Switch } from 'react-native';
const DATA = [
{ id: '101', name: 'レポートA', type: 'document' },
{ id: '102', name: '写真B', type: 'image' },
{ id: '103', name: '音声C', type: 'audio' },
{ id: '104', name: 'ビデオD', type: 'video' },
];
// Contextの作成
const ThemeContext = createContext(null);
const DarkModeItem = memo(({ item }) => {
// ContextからisDarkModeを取得
const { isDarkMode } = useContext(ThemeContext);
console.log(`アイテム ${item.name} がレンダリングされました (Context: ${isDarkMode})`);
const itemStyle = isDarkMode ? styles.darkItem : styles.lightItem;
const textStyle = isDarkMode ? styles.darkText : styles.lightText;
return (
<View style={[styles.itemBase, itemStyle]}>
<Text style={textStyle}>{item.name}</Text>
<Text style={textStyle}>タイプ: {item.type}</Text>
</View>
);
});
const ContextAlternative = () => {
const [isDarkMode, setIsDarkMode] = useState(false);
const renderItem = useCallback(
({ item }) => (
<DarkModeItem item={item} />
),
[] // DarkModeItem は Context から isDarkMode を取得するため、extraDataも不要
);
return (
// Context ProviderでisDarkModeとsetIsDarkModeを提供
<ThemeContext.Provider value={{ isDarkMode, setIsDarkMode }}>
<View style={{ flex: 1, backgroundColor: isDarkMode ? '#333' : '#f0f0f0' }}>
<View style={styles.themeToggleContainer}>
<Text style={styles.themeToggleText}>ダークモード:</Text>
<Switch
onValueChange={setIsDarkMode}
value={isDarkMode}
trackColor={{ false: "#767577", true: "#81b0ff" }}
thumbColor={isDarkMode ? "#f5dd4b" : "#f4f3f4"}
/>
</View>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={(item) => item.id}
// extraData は不要! 各アイテムが Context から isDarkMode を取得する
ListHeaderComponent={() => (
<Text style={[styles.header, { color: isDarkMode ? 'white' : 'black' }]}>
Contextによる代替
</Text>
)}
/>
</View>
</ThemeContext.Provider>
);
};
const styles = StyleSheet.create({
header: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginVertical: 10,
},
themeToggleContainer: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
padding: 10,
backgroundColor: '#ddd',
marginVertical: 5,
},
themeToggleText: {
marginRight: 10,
fontSize: 16,
},
itemBase: {
padding: 20,
marginVertical: 8,
marginHorizontal: 16,
borderRadius: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.2,
shadowRadius: 1.41,
elevation: 2,
},
lightItem: {
backgroundColor: '#fff',
},
darkItem: {
backgroundColor: '#555',
},
lightText: {
color: 'black',
},
darkText: {
color: 'white',
},
});
export default ContextAlternative;
メリット
- スケーラビリティ
大規模なアプリケーションで複雑な状態管理を行う場合に適しています。 - プロップドリリングの回避
中間コンポーネントを介してプロップをバケツリレーする「プロップドリリング」を回避できます。 - グローバルな状態管理
アプリケーションの複数の箇所で共有される状態を効率的に管理できます。
- 再レンダリング
Context やストアの値が頻繁に更新されると、それを購読している多くのコンポーネントが再レンダリングされる可能性があります。useMemo
やReact.memo
を適切に使用して最適化する必要があります。 - オーバーヘッド
小規模なアプリケーションや、特定のFlatList
のためだけに導入するには、設定や学習のオーバーヘッドがあります。
-
Context API や状態管理ライブラリが推奨される場合
- アプリケーション全体で共有されるグローバルな状態(テーマ、認証情報、ユーザー設定など)がリストアイテムの表示に影響を与える場合。
- 複数の異なるコンポーネントが同じ状態に依存しており、プロップドリリングを避けたい場合。
-
各アイテムコンポーネントでの状態管理 (React.memo と組み合わせる) が推奨される場合
- 各リストアイテムが自身の内部的なUI状態(例: 展開/折りたたみ、個別のチェックボックスの状態など)を持つ場合。
- その状態が他のアイテムや親コンポーネントから独立している場合。
- リストが長く、スクロールパフォーマンスが非常に重要で、個々のアイテムの変更が他のアイテムに影響しない場合。
-
data プロパティの更新が推奨される場合
- リストアイテムのデータそのものが変更される、追加される、削除される、並べ替えられるなどの場合。
- アイテムの表示/非表示を切り替えるフィルターのように、実際に表示されるアイテムのセットが変更される場合。
-
extraData の使用が適切と思われる場合
- リストアイテムの見た目を変えるために、
data
プロパティの配列外にある単一の、比較的シンプルな状態に依存している場合(例:selectedId
)。 - その状態が頻繁に更新されない場合。
data
プロパティ自体を更新すると、不必要に複雑になる場合。
- リストアイテムの見た目を変えるために、