React Nativeリスト完全ガイド:FlatList・FlashList・Legend List徹底比較【2026年版】

React NativeのFlatList、FlashList、Legend Listを徹底比較。セルリサイクリングの仕組み、移行手順、パフォーマンス最適化テクニックを実際のコード例で解説します。

はじめに:リストのパフォーマンス、甘く見てませんか?

React Nativeでアプリを作っていると、リスト表示って本当にあらゆる場面で出てきますよね。SNSのフィード、商品カタログ、チャット画面、設定メニュー——数え上げたらキリがないです。

で、ここが厄介なポイントなんですが、大量のデータを扱うリストのパフォーマンスは、アプリ全体のユーザー体験を左右します。正直、リストがカクついたらどんなに他の部分が良くても台無しです。

2026年現在、React Nativeのリストコンポーネントには主に3つの選択肢があります。標準のFlatList、Shopifyが開発したFlashList、そして比較的新しく登場したLegend Listです。この記事では、それぞれの仕組みや特徴、パフォーマンスを実際のコード例を交えながら比較していきます。

では、さっそく見ていきましょう。

FlatList:React Native標準のリストコンポーネント

FlatListの基本的な仕組み

FlatListはReact Nativeに標準搭載されている仮想化リストコンポーネントです。内部的にはVirtualizedListをベースにしていて、「仮想化(Virtualization)」という技術で画面に表示されているアイテムとそのバッファ領域だけをレンダリングします。

まずは基本的な使い方から。

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

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

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

  return (
    <FlatList
      data={DATA}
      renderItem={renderItem}
      keyExtractor={(item) => item.id}
      initialNumToRender={10}
      maxToRenderPerBatch={10}
      windowSize={5}
    />
  );
};

const styles = StyleSheet.create({
  item: {
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: '#eee',
  },
  title: {
    fontSize: 16,
  },
});

export default App;

FlatListの限界

FlatListは小〜中規模のリストなら問題ないんですが、以下のような状況ではパフォーマンスの問題がはっきりと出てきます。

  • 大量データ(1,000件以上):スクロール中にフレームドロップが起きやすい
  • 複雑なアイテムレイアウト:画像やネストされたコンポーネントを含むアイテムは、マウント/アンマウントのコストが重い
  • ローエンドのAndroidデバイス:JSスレッドのCPU使用率が90%超になって、スクロールがガタガタになることも
  • 空白セルの問題:高速スクロールすると、アイテムが描画される前に空白が見えてしまう

ある検証では、FlatListのJSスレッドFPSが平均9.28まで落ちた事例が報告されています。ユーザーは60FPSのスムーズなスクロールを期待しているわけで、この数値はかなり深刻ですよね。

FlatListのパフォーマンスチューニングProps

FlatListにはパフォーマンスを調整するためのPropsがいくつかあります。知っておくと便利です。

<FlatList
  data={DATA}
  renderItem={renderItem}
  keyExtractor={(item) => item.id}
  // 初期レンダリング数(デフォルト: 10)
  initialNumToRender={10}
  // バッチあたりの最大レンダリング数(デフォルト: 10)
  maxToRenderPerBatch={5}
  // ウィンドウサイズ(表示領域の倍数)
  windowSize={5}
  // 表示領域外のアイテムをクリップ
  removeClippedSubviews={true}
  // アイテムサイズが既知の場合にレイアウト計算をスキップ
  getItemLayout={(data, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  })}
/>

これらを適切に設定すればある程度改善できますが、FlatListの根本的な設計——アイテムのマウントとアンマウントを繰り返す仕組み——自体は変えられません。ここが限界です。

FlashList:セルリサイクリングで劇的に高速化

FlashListの革新的なアプローチ

FlashListは、Shopifyのエンジニアリングチームが開発した高パフォーマンスリストコンポーネントです。

FlatListとの最大の違いは、セルリサイクリング(Cell Recycling)という戦略にあります。FlatListが画面外のアイテムを「破棄して再作成」するのに対し、FlashListはコンポーネントインスタンスの固定プールをメモリに保持して、スクロールで画面外に出たアイテムを新しいデータで再利用します。

つまり、コストの高いマウント/アンマウントのサイクルがなくなるわけです。この違いがパフォーマンスに与える影響は想像以上に大きいですよ。

FlashListのインストールと基本的な使い方

# インストール
npx expo install @shopify/flash-list

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

嬉しいことに、FlashListはFlatListのドロップイン代替品として設計されています。最小限のコード変更で移行できるので、試すハードルがかなり低いです。

import React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import { FlashList } from '@shopify/flash-list';

const DATA = Array.from({ length: 10000 }, (_, i) => ({
  id: String(i),
  title: `アイテム ${i + 1}`,
  type: i % 3 === 0 ? 'featured' : 'normal',
}));

const App = () => {
  const renderItem = ({ item }) => (
    <View style={[
      styles.item,
      item.type === 'featured' && styles.featured
    ]}>
      <Text style={styles.title}>{item.title}</Text>
    </View>
  );

  return (
    <FlashList
      data={DATA}
      renderItem={renderItem}
      estimatedItemSize={60}
      getItemType={(item) => item.type}
    />
  );
};

const styles = StyleSheet.create({
  item: {
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: '#eee',
  },
  featured: {
    backgroundColor: '#f0f8ff',
  },
  title: {
    fontSize: 16,
  },
});

export default App;

FlashListの重要なProps

FlashListのパフォーマンスを最大限引き出すために、押さえておきたいPropsを紹介します。

  • estimatedItemSize:アイテムの推定サイズ(ピクセル単位)。レンダリング前のレイアウト最適化に使われます。全アイテムが同じサイズなら正確な値を、異なる場合は平均値か中央値を設定しましょう。
  • getItemType:異なるタイプのアイテムを指定することで、リサイクルプールを効率的に管理できます。レイアウトが異なるアイテムが混在してる場合に特に効果的。
  • overrideItemLayout:特定のアイテムのサイズやカラムスパンをオーバーライドできます。グリッドで特定のアイテムを複数カラムにまたがらせたいときに便利です。

FlashList v2の新機能(新アーキテクチャ対応)

FlashList v2はReact Nativeの新アーキテクチャ(Fabric + JSI)向けにゼロから再構築されたバージョンです。かなりの改善が入っています。

  • サイズ推定が不要に:v1で必須だったestimatedItemSizeが不要に。アイテムサイズを自動処理してくれます
  • マソンリーレイアウト:Pinterestスタイルのレイアウトをネイティブサポート
  • maintainVisibleContentPosition:アイテム追加時にコンテンツがずれるのを自動補正(デフォルトで有効)
  • JSのみのソリューション:ネイティブモジュール不要で、より軽量になりました
// FlashList v2 マソンリーレイアウトの例
import { MasonryFlashList } from '@shopify/flash-list';

const MasonryExample = () => (
  <MasonryFlashList
    data={imageData}
    numColumns={2}
    renderItem={({ item }) => (
      <View style={{ height: item.height }}>
        <Image source={{ uri: item.uri }} style={{ flex: 1 }} />
      </View>
    )}
    estimatedItemSize={150}
  />
);

注意:FlashList v2.xは新アーキテクチャ専用です。旧アーキテクチャのプロジェクトではv1.xを使いましょう。

Legend List:100% JSで書かれた次世代リスト

Legend Listとは

Legend Listは、LegendAppチームが開発した高パフォーマンスリストコンポーネントです。2026年にバージョン1.0がリリースされ、「最速のReact Nativeリストライブラリ」を謳っています。

個人的に一番インパクトがあるのは、100% TypeScriptで書かれていてネイティブ依存が一切ないという点です。これ、地味にすごくないですか?

Legend Listの特徴

  • 動的アイテムサイズのネイティブサポート:高さが異なるアイテムをパフォーマンス低下なしに処理。estimatedItemSizeの設定すら不要です
  • オプショナルなリサイクリングrecycleItemsプロパティでリサイクルの有効/無効を切り替え可能。内部状態を持つ複雑なアイテムにも柔軟に対応できます
  • チャットUI向け機能maintainScrollAtEndalignItemsAtEndのおかげで、チャットアプリの実装がかなり楽になります
  • 双方向無限スクロール:上下両方向への無限スクロールをネイティブにサポート
  • 新旧アーキテクチャの両方に対応:FlashList v2と違って、旧アーキテクチャでも問題なく動作します

Legend Listのインストールと使い方

# インストール
npm install @legendapp/list
# または
yarn add @legendapp/list
import React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import { LegendList } from '@legendapp/list';

const DATA = Array.from({ length: 10000 }, (_, i) => ({
  id: String(i),
  title: `アイテム ${i + 1}`,
  description: `これはアイテム ${i + 1} の説明文です。`,
}));

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

  return (
    <LegendList
      data={DATA}
      renderItem={renderItem}
      keyExtractor={(item) => item.id}
      recycleItems
      maintainScrollAtEnd={false}
    />
  );
};

const styles = StyleSheet.create({
  item: {
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: '#eee',
  },
  title: {
    fontSize: 16,
    fontWeight: 'bold',
  },
  description: {
    fontSize: 14,
    color: '#666',
    marginTop: 4,
  },
});

export default App;

チャットUIの実装例

Legend Listが特に力を発揮するのがチャットUIです。

これまでReact Nativeでチャットリストを実装するときは、invertedプロパティによるリスト反転ハックが定番でした(あれ、正直あまり気持ちのいい実装じゃなかったですよね)。Legend Listではそれが不要になります。

import { LegendList } from '@legendapp/list';

const ChatScreen = ({ messages }) => {
  return (
    <LegendList
      data={messages}
      renderItem={({ item }) => (
        <View style={[
          styles.messageBubble,
          item.isOwn ? styles.ownMessage : styles.otherMessage,
        ]}>
          <Text>{item.text}</Text>
          <Text style={styles.timestamp}>{item.time}</Text>
        </View>
      )}
      keyExtractor={(item) => item.id}
      alignItemsAtEnd
      maintainScrollAtEnd
      recycleItems
    />
  );
};

3つのリストコンポーネント徹底比較

アーキテクチャの違い

まずは設計面での違いを整理してみましょう。

特徴FlatListFlashList v2Legend List
レンダリング戦略仮想化(マウント/アンマウント)セルリサイクリング仮想化 + オプショナルリサイクリング
ネイティブ依存なし(標準搭載)なし(v2からJS only)なし(100% TypeScript)
新アーキテクチャ対応対応v2は新アーキテクチャ専用新旧両対応
動的アイテムサイズgetItemLayoutで対応v2で自動対応ネイティブ対応
マソンリーレイアウト非対応v2で対応非対応
チャットUI機能invertedのみ基本的な対応専用Props(maintainScrollAtEnd等)
リサイクリング制御不可常にリサイクルrecycleItemsで切替可能

パフォーマンス比較

次に気になるパフォーマンスの数値です。

指標FlatListFlashListLegend List
10,000件のスクロールFPS約9〜30 FPS約55〜60 FPS約55〜60 FPS
初期レンダリング速度基準FlatListの約5〜10倍高速FlatListより高速
メモリ使用量高い低い(リサイクルによる)低い
JSスレッドCPU使用率高い(90%以上の事例あり)低い(10%以下に改善)低い
空白セルの発生頻繁ほぼなしほぼなし

FlashListとLegend ListのFPS差はほとんどありません。どちらも60FPSに近い数値を安定して出せます。FlatListとの差は歴然ですね。

FlatListからの移行ガイド

FlashListへの移行(3ステップ)

FlashListはドロップイン代替品なので、移行はとてもシンプルです。実際にやってみると「え、これだけ?」ってなると思います。

// ステップ1: インポートの変更
- import { FlatList } from 'react-native';
+ import { FlashList } from '@shopify/flash-list';

// ステップ2: コンポーネント名の変更 + estimatedItemSize の追加
- <FlatList
+ <FlashList
    data={data}
    renderItem={renderItem}
-   keyExtractor={(item) => item.id}
+   estimatedItemSize={60}
  />

// ステップ3: getItemType の追加(異なるレイアウトがある場合)
<FlashList
  data={data}
  renderItem={renderItem}
  estimatedItemSize={60}
+ getItemType={(item) => item.type}
/>

移行時の注意点

  • アイテムコンポーネントにkey propを設定しないでください。keyを使うとFlashListがビューをリサイクルできなくなって、パフォーマンスの利点が消えます
  • React.memoでアイテムコンポーネントをラップするのがおすすめです
  • FlashListはラップする親Viewにサイズが必要です。flex: 1を設定するか、明示的な高さを指定してください

Legend Listへの移行

Legend Listへの移行も同じくらい簡単です。FlashListからの乗り換えもスムーズにできます。

// FlatList からの移行
- import { FlatList } from 'react-native';
+ import { LegendList } from '@legendapp/list';

- <FlatList
+ <LegendList
    data={data}
    renderItem={renderItem}
    keyExtractor={(item) => item.id}
+   recycleItems
  />

// FlashList からの移行
- import { FlashList } from '@shopify/flash-list';
+ import { LegendList } from '@legendapp/list';

- <FlashList
+ <LegendList
    data={data}
    renderItem={renderItem}
-   estimatedItemSize={60}
+   recycleItems
  />

実践的な最適化テクニック

1. React.memoで不要な再レンダリングを防ぐ

どのリストコンポーネントを使う場合でも、React.memoによるアイテムのメモ化は基本中の基本です。これをやるかやらないかで体感がかなり変わります。

import React, { memo, useCallback } from 'react';
import { Text, View, Pressable, StyleSheet } from 'react-native';
import { FlashList } from '@shopify/flash-list';

// アイテムコンポーネントをmemo化
const ListItem = memo(({ item, onPress }) => (
  <Pressable onPress={() => onPress(item.id)}>
    <View style={styles.item}>
      <Text style={styles.title}>{item.title}</Text>
      <Text style={styles.subtitle}>{item.subtitle}</Text>
    </View>
  </Pressable>
));

const OptimizedList = ({ data }) => {
  // コールバックもuseCallbackでメモ化
  const handlePress = useCallback((id) => {
    console.log('Pressed:', id);
  }, []);

  const renderItem = useCallback(({ item }) => (
    <ListItem item={item} onPress={handlePress} />
  ), [handlePress]);

  return (
    <FlashList
      data={data}
      renderItem={renderItem}
      estimatedItemSize={80}
    />
  );
};

2. 画像の最適化

リスト内に画像がある場合、読み込みと描画のパフォーマンスは無視できません。expo-imageを使うとキャッシュやプレースホルダーの管理がぐっと楽になります。

import { Image } from 'expo-image';

const ListItemWithImage = memo(({ item }) => (
  <View style={styles.item}>
    <Image
      source={{ uri: item.imageUrl }}
      style={styles.thumbnail}
      placeholder={{ blurhash: item.blurhash }}
      contentFit="cover"
      transition={200}
      recyclingKey={item.id}
    />
    <View style={styles.textContainer}>
      <Text style={styles.title}>{item.title}</Text>
    </View>
  </View>
));

3. 無限スクロール(ページネーション)の実装

大量のデータを一気に読み込むのではなく、ページネーションで段階的に取得するのがセオリーです。初期表示の速度とメモリの両方に効きます。

import React, { useState, useCallback } from 'react';
import { ActivityIndicator, View } from 'react-native';
import { FlashList } from '@shopify/flash-list';

const PAGE_SIZE = 20;

const InfiniteScrollList = () => {
  const [data, setData] = useState(initialData);
  const [loading, setLoading] = useState(false);
  const [page, setPage] = useState(1);

  const loadMore = useCallback(async () => {
    if (loading) return;
    setLoading(true);

    const nextPage = page + 1;
    const newItems = await fetchItems(nextPage, PAGE_SIZE);

    setData((prev) => [...prev, ...newItems]);
    setPage(nextPage);
    setLoading(false);
  }, [loading, page]);

  const renderFooter = useCallback(() => {
    if (!loading) return null;
    return (
      <View style={{ padding: 16, alignItems: 'center' }}>
        <ActivityIndicator size="small" />
      </View>
    );
  }, [loading]);

  return (
    <FlashList
      data={data}
      renderItem={renderItem}
      estimatedItemSize={80}
      onEndReached={loadMore}
      onEndReachedThreshold={0.5}
      ListFooterComponent={renderFooter}
    />
  );
};

4. セクション付きリストの実装

FlashListでセクションヘッダー付きのリストを実装するなら、getItemTypeを活用するのがポイントです。リサイクリングも効率的に行えます。

import { FlashList } from '@shopify/flash-list';

// セクションとアイテムを統合したフラットなデータ構造
const flattenedData = [
  { type: 'header', title: 'カテゴリA' },
  { type: 'item', id: '1', name: 'アイテム1' },
  { type: 'item', id: '2', name: 'アイテム2' },
  { type: 'header', title: 'カテゴリB' },
  { type: 'item', id: '3', name: 'アイテム3' },
];

const SectionList = () => (
  <FlashList
    data={flattenedData}
    renderItem={({ item }) => {
      if (item.type === 'header') {
        return (
          <View style={styles.sectionHeader}>
            <Text style={styles.sectionTitle}>{item.title}</Text>
          </View>
        );
      }
      return (
        <View style={styles.item}>
          <Text>{item.name}</Text>
        </View>
      );
    }}
    getItemType={(item) => item.type}
    estimatedItemSize={50}
    stickyHeaderIndices={
      flattenedData
        .map((item, index) => (item.type === 'header' ? index : null))
        .filter((index) => index !== null)
    }
  />
);

ユースケース別のおすすめリストコンポーネント

結局どれを使えばいいの?という方のために、ユースケース別にまとめました。

ユースケースおすすめ理由
設定画面(〜50件)FlatListシンプルで十分。追加ライブラリ不要
商品カタログ(100〜1,000件)FlashListセルリサイクリングで安定した60FPS
SNSフィード(無限スクロール)FlashList / Legend List大量データの高速レンダリング
チャットUILegend List専用のチャット機能が充実
画像グリッド(Pinterest風)FlashList v2マソンリーレイアウト対応
動的高さのアイテムLegend ListestimatedItemSize不要で自然にサポート
旧アーキテクチャのプロジェクトFlashList v1 / Legend ListFlashList v2は新アーキテクチャ専用

よくある質問(FAQ)

FlatListからFlashListに移行するとアプリが壊れませんか?

基本的には大丈夫です。FlashListはFlatListのドロップイン代替品として設計されているので、ほとんどの場合はコンポーネント名の変更とestimatedItemSizeの追加だけで移行できます。ただし、アイテムコンポーネントにkey propを直接設定している場合は削除が必要です。念のため、移行前にテスト環境で動作確認しておくのをおすすめします。

FlashList v2とLegend Listのどちらを選ぶべきですか?

プロジェクトの要件次第です。新アーキテクチャを使っていてマソンリーレイアウトが必要ならFlashList v2がいいでしょう。チャットUIを作りたい場合や、旧アーキテクチャのプロジェクトならLegend Listのほうが向いています。パフォーマンス自体はどちらもしっかり高いので、必要な機能ベースで選んでOKです。

estimatedItemSizeにはどんな値を設定すればよいですか?

FlashList v1で必要なestimatedItemSizeには、リストアイテムの平均的な高さ(横スクロールなら幅)をピクセル単位で設定します。全アイテムが同じサイズならその正確な値を、サイズが異なるなら中央値がベストです。ちなみにFlashList v2とLegend Listではこのプロパティ自体が不要になっています。

新アーキテクチャに移行してない場合はどうすれば?

旧アーキテクチャのままなら、FlashList v1.xかLegend Listを使いましょう。FlashList v2.xは新アーキテクチャ(Fabric + JSI)専用なので旧環境では動きません。Legend Listは新旧どちらにも対応しているので、将来的に新アーキテクチャへの移行を考えている場合にも安心です。

リストのパフォーマンスはどうやって計測する?

React NativeのPerformanceモニターでJSスレッドとUIスレッドのFPSをリアルタイム監視できます。開発メニューから「Show Perf Monitor」を有効にしてください。FlashListには組み込みのパフォーマンス警告もあって、estimatedItemSizeの値が大きくずれていればコンソールに教えてくれます。本番環境の計測にはFlipperやreact-native-performanceなどのライブラリがおすすめです。

著者について Editorial Team

Our team of expert writers and editors.