FlatList#extraData

2025-05-31

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={...} /> 

このように、変更を検知したい特定のステート変数やプロパティのみを渡すことで、無駄な再レンダリングを防ぎ、パフォーマンスを最適化できます。オブジェクトを渡す場合は、そのオブジェクト自体が新しい参照になるように(例えば useStateuseReducer で正しく更新されるように)注意が必要です。

不必要な再レンダリングによるパフォーマンスの低下

問題
extraData を使用しているにもかかわらず、リストのスクロールがカクついたり、パフォーマンスが低い。

原因
extraData に渡す値が頻繁に、かつ不必要に更新されている可能性があります。extraDataFlatList のシャロー比較の対象となるため、値が少しでも変わると 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環境の場合)

問題
useStateuseReducer を使用している場合でも、extraData がうまく機能しないように見える。

原因
Hooks の使い方自体に問題があるか、extraData に渡される値が実際に変更されていないにもかかわらず、変更されたと思い込んでいる場合があります。

トラブルシューティング/解決策

  • オブジェクトの参照
    extraData にオブジェクトを渡している場合、そのオブジェクトの参照が毎回新しいものになっていることを確認します。例えば、extraData={{ count: myCount }} のように直接オブジェクトリテラルを渡すと、myCount が変わるたびに新しいオブジェクトが作成されるため、FlatList は変更を検知します。しかし、extraData={myObject} のように、myObject のプロパティが変わっても myObject 自体の参照が変わらない場合、FlatList は変更を検知しません。この場合は useMemo が有効です。
  • ステートの更新を確認
    useState のセッター関数が正しく呼ばれ、ステートが実際に更新されているか確認します。

FlatList#extraData は、data プロパティに含まれない状態の変更によってリストアイテムのUIを更新する必要がある場合に非常に便利ですが、その動作原理(シャロー比較)を理解し、適切に使用することが重要です。

  • パフォーマンス
    不必要な再レンダリングを防ぐために、extraData に渡す値を適切に管理し、renderItem やリストアイテムコンポーネント自体を最適化する。
  • 何を渡すか
    renderItem が依存する、data 以外の、変更時に再レンダリングをトリガーしたい最小限の値(プリミティブ値、または新しい参照を持つオブジェクト)。
  • いつ使うか
    FlatListdata プロパティの配列自体は変更されないが、リストアイテムの見た目や挙動に影響を与える別の状態が変更された場合。


FlatList#extraData は、FlatListdata プロパティの配列以外の変更を検知し、リストアイテムの再レンダリングをトリガーするために使用されます。これにより、リスト内の個々のアイテムの表示が、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;

解説

  1. selectedId ステート
    どのアイテムが現在選択されているかを管理するために selectedId というステートを使用しています。
  2. Item コンポーネントのメモ化
    React.memo を使用して Item コンポーネントをメモ化しています。これにより、item, onPress, backgroundColor, textColor のプロップが変更されない限り、Item コンポーネントは再レンダリングされません。
  3. renderItem の useCallback
    renderItem 関数自体も useCallback でメモ化しています。これにより、FlatList が不要な再レンダリングを避けることができます。renderItemselectedId に依存するため、依存配列に selectedId を含めています。
  4. extraData={selectedId}
    これが最も重要な部分です。selectedIddata プロパティの配列の一部ではありませんが、リストアイテムの見た目(背景色)に影響を与えます。
    • selectedId の値が変更されると、FlatListextraData の変更を検知します。
    • 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 プロパティ自体が変更される場合は必要ありません。
  • FlatListdata プロパティの変更(ここでは新しい配列の参照)を自動的に検知し、適切に再レンダリングを行います。
  • なぜなら、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;

解説

  1. isDarkMode ステート
    アプリ全体のテーマを制御する isDarkMode ステートがあります。
  2. DarkModeItem のメモ化
    DarkModeItemitemisDarkMode のプロップに依存します。isDarkMode が変わると、すべての DarkModeItem が再レンダリングされ、テーマが切り替わります。
  3. extraData={isDarkMode}
    ここで isDarkModeextraData に渡しています。
    • isDarkMode の値が truefalse の間で切り替わるたびに、FlatListextraData の変更を検知します。
    • これにより、FlatList はすべてのリストアイテムの renderItem を再度実行し、新しいテーマに応じたスタイルが適用されます。
    • FlatListisDarkMode の変更を検知しなければ、リストアイテムの見た目は変わらないままになります。

これらの例からわかるように、FlatList#extraData は主に以下のシナリオで役立ちます。

  • FlatListPureComponent のように動作するため、プロパティのシャロー比較で変更を検知できない場合に、明示的に再レンダリングを促す必要がある。
  • data プロパティの配列自体は変更されないが、リストアイテムの見た目や動作に影響を与える外部の状態(選択状態、テーマ、表示モードなど)がある場合。


data プロパティの更新

最も直接的で、多くの場合に推奨される方法です。リストアイテムのレンダリングに影響を与える状態が変更された際に、FlatListdata プロパティ自体を新しい配列で更新します。

どのように機能するか
React の原則に従い、UIはステートから計算されます。FlatListdata プロパティに渡された配列の参照を監視しています。この参照が新しいものに変われば、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;

メリット

  • 信頼性
    FlatListdata プロパティの変更を最も効率的に処理するように設計されています。
  • クリーン
    extraData のような追加のプロパティを管理する必要がありません。
  • 直感的
    React のデータフローに最も合致しています。

デメリット

  • renderItem の内部でしか使われない一時的なUI状態(例:アイテム内のサブコンポーネントの開閉状態など)を data に含めるのは、データの純粋性を損なう可能性があります。
  • data 配列の要素数が多い場合、毎回新しい配列を作成するオーバーヘッドが発生する可能性があります(ただし、通常はReactの仮想DOMと再調整アルゴリズムによって最適化されます)。

各アイテムコンポーネントでの状態管理と React.memo

各リストアイテムが自身に関連する特定のUI状態を管理し、その状態変更に応じて自身を再レンダリングするようにします。FlatList には、アイテムのレンダリングに必要な最小限のデータのみを渡し、残りのUI状態は各アイテムコンポーネント内部で管理します。

どのように機能するか
FlatListrenderItem から渡される 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 やストアから必要な状態(例:isDarkModeselectedItems のセット)を購読し、その状態が変更された場合に自身を再レンダリングします。


ダークモードの切り替えを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 やストアの値が頻繁に更新されると、それを購読している多くのコンポーネントが再レンダリングされる可能性があります。useMemoReact.memo を適切に使用して最適化する必要があります。
  • オーバーヘッド
    小規模なアプリケーションや、特定の FlatList のためだけに導入するには、設定や学習のオーバーヘッドがあります。
  • Context API や状態管理ライブラリが推奨される場合

    • アプリケーション全体で共有されるグローバルな状態(テーマ、認証情報、ユーザー設定など)がリストアイテムの表示に影響を与える場合。
    • 複数の異なるコンポーネントが同じ状態に依存しており、プロップドリリングを避けたい場合。
  • 各アイテムコンポーネントでの状態管理 (React.memo と組み合わせる) が推奨される場合

    • 各リストアイテムが自身の内部的なUI状態(例: 展開/折りたたみ、個別のチェックボックスの状態など)を持つ場合。
    • その状態が他のアイテムや親コンポーネントから独立している場合。
    • リストが長く、スクロールパフォーマンスが非常に重要で、個々のアイテムの変更が他のアイテムに影響しない場合。
  • data プロパティの更新が推奨される場合

    • リストアイテムのデータそのものが変更される、追加される、削除される、並べ替えられるなどの場合。
    • アイテムの表示/非表示を切り替えるフィルターのように、実際に表示されるアイテムのセットが変更される場合。
  • extraData の使用が適切と思われる場合

    • リストアイテムの見た目を変えるために、data プロパティの配列外にある単一の、比較的シンプルな状態に依存している場合(例: selectedId)。
    • その状態が頻繁に更新されない場合。
    • data プロパティ自体を更新すると、不必要に複雑になる場合。