Reactの基礎 - リストレンダリング

提供: MochiuWiki : SUSE, EC, PCB

2026年2月25日 (水) 07:39時点におけるWiki (トーク | 投稿記録)による版 (ページの作成:「== 概要 == Reactにおけるリストレンダリングとは、配列データをもとに複数のTSX要素を動的に生成して表示する技術である。<br> <br> TypeScriptの <code>map()</code> 関数を使用することにより、配列の各要素をTSX要素に変換して画面に表示できる。<br> <br> リストレンダリングを定義する場合は、各要素に <code>key</code> 属性を付与することが必須となる。<br> <cod…」)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)

📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは2026年9月に閉鎖いたします。
新しい記事は移転先で追加しております。(旧サイトでは記事を追加しておりません)

概要

Reactにおけるリストレンダリングとは、配列データをもとに複数のTSX要素を動的に生成して表示する技術である。

TypeScriptの map() 関数を使用することにより、配列の各要素をTSX要素に変換して画面に表示できる。

リストレンダリングを定義する場合は、各要素に key 属性を付与することが必須となる。
key 属性は、ReactがどのコンポーネントIDが変化したかを識別するために使用する特別な属性であり、適切に設定することでパフォーマンスの最適化とバグの防止につながる。

また、filter() 関数と組み合わせることで条件付きのリストレンダリングを実現でき、入れ子の配列を扱うネストしたリストも実装できる。

下表に、リストレンダリングの手法を示す。

リストレンダリングの手法
テクニック
map() を使用した配列要素のTSX変換
key 属性によるコンポーネント同一性の管理
filter() との組み合わせによる条件付き表示
ネストした配列の再帰的なレンダリング
大規模リストに対する仮想化ライブラリの活用



map() による繰り返し描画

TypeScriptの map() 関数は、配列の各要素を変換して新しい配列を返す。

Reactでは、この特性を利用して配列データをTSX要素の配列に変換して、コンポーネントとして描画する。

リストレンダリングの基本

文字列配列を map() でリスト要素に変換する基本的な例を以下に示す。

 const people: string[] = ['Alice', 'Bob', 'Charlie'];
 
 export default function List() {
    const listItems = people.map(person =>
       <li key={person}>{person}</li>
    );
    return <ul>{listItems}</ul>;
 }


上記の例では、people 配列の各文字列を

  • 要素に変換している。
    文字列配列の場合は、値自体が一意であれば key 属性の値として使用できる。

    数値配列の場合も同様に、map() で各数値をリスト要素に変換できる。

     const numbers: number[] = [1, 2, 3, 4, 5];
     
     export default function NumberList() {
        const listItems = numbers.map(number =>
           <li key={number}>{number}</li>
        );
        return <ul>{listItems}</ul>;
     }
    



    オブジェクト配列のレンダリング

    実際のアプリケーションでは、ユーザ情報等のオブジェクト配列を扱うことが多い。

    オブジェクト配列のレンダリング例を以下に示す。

     interface Person {
        id: number;
        name: string;
        profession: string;
     }
     
     const people: Person[] = [
        { id: 0, name: 'Creola Katherine Johnson', profession: 'mathematician' },
        { id: 1, name: 'Mario José Molina-Pasquel', profession: 'chemist' },
        { id: 2, name: 'Mohammad Abdus Salam', profession: 'physicist' },
     ];
     
     export default function List() {
        const listItems = people.map(person =>
           <li key={person.id}>
              <p>{person.name}: {person.profession}</p>
           </li>
        );
        return <ul>{listItems}</ul>;
     }
    


    オブジェクト配列では、各オブジェクトが持つ一意のID (上記では、person.id) を key 属性に使用するのが最適である。
    map() のコールバック関数内では、オブジェクトの各プロパティにアクセスして表示内容を構成できる。


    key属性

    key 属性は、Reactがリストの各アイテムを識別するために使用する特別な属性である。
    適切な key を設定することで、リストの更新・並べ替え・削除を効率的に処理できる。

    keyの役割

    key 属性は、Reactの差分検出アルゴリズム (Reconciliation) において重要な役割がある。

    Reconciliationとは、ReactがVirtual DOMと実際のDOMの差分を検出して、必要な箇所のみを更新するプロセスである。
    key 属性の働きは以下の通りである。

    • コンポーネントの同一性識別
      Reactは key の一致によってコンポーネントが同一のデータを表しているかを識別する。
      これはファイルシステムにおけるファイル名に相当する概念であり、名前が変わればReactは別のコンポーネントと判断する。

    • 状態の保持
      データの順序が変わっても、key が同じであればコンポーネントの状態 (state) が保持される。
      key が無い場合は位置 (インデックス) で識別されるため、順序変更時にフォーム入力値等の状態が誤ったアイテムに紐づくバグが発生する。

    • 再マウントの制御
      key が変わった場合、ReactはそのコンポーネントをアンマウントしてDOMから削除し、新しいコンポーネントとして再マウントする。
      これにより、以前の状態は引き継がれずにリセットされる。


    keyの選定基準

    下表に、適切な key と不適切な key の基準を示す。

    keyの適否
    種別 説明
    適切 (最適) key={person.id} データベースのID等、データが持つ一意の識別子を使用する。最も推奨される方法。
    適切 key={crypto.randomUUID()} ローカルで生成する場合は、インクリメントカウンタやUUIDを使用する。
    ただし、コンポーネントの初期化時に1度だけ生成してstateに保持すること。
    不適切 key={index} 配列のインデックスをkeyに使用してはならない。
    アイテムの削除や並べ替えが発生した場合に、フォームの入力値が誤ったアイテムに紐づくバグが生じる。
    絶対禁止 key={Math.random()} レンダリングのたびにkeyが変わるため、全コンポーネントとDOMが毎回再作成される。
    パフォーマンスが著しく低下する。
    禁止 レンダリング中のkey生成 関数の実行のたびにkeyが変わるため、コンポーネントが毎回再マウントされてしまう。


    keyの制約

    key 属性には以下に示す制約がある。

    key 属性に関する制約
    ルール 説明
    兄弟要素間でのみ一意であればよい
    • key はグローバルに一意である必要はなく、同じリスト内の兄弟要素間でのみ一意であれば問題ない。
    • 別のリストや別のコンポーネントツリーでは、同じ key 値を使用できる。
    keyは不変でなければならない レンダリング中に key が変わってはならない。
    不変の値のみを key に使用すること。
    keyはpropsとして受け取れない
    • key はReact内部で使用される特別な属性であり、コンポーネントの props としてアクセスすることはできない。
    • コンポーネントにIDを渡す場合は、<Profile key={id} userId={id} /> のように別のpropを使用して渡す。



    filter() との組み合わせ

    filter() 関数は、配列の要素をある条件でフィルタリングして新しい配列を返す。
    map() と組み合わせることにより、条件に合致するアイテムのみを表示できる。

    条件付きリストレンダリング

    filter()map() を組み合わせた条件付きレンダリングの例を以下に示す。

     // Person型は上記で定義済み
     const people: Person[] = [
        { id: 0, name: 'Creola Katherine Johnson', profession: 'mathematician' },
        { id: 1, name: 'Mario José Molina-Pasquel', profession: 'chemist' },
        { id: 2, name: 'Mohammad Abdus Salam', profession: 'physicist' },
        { id: 3, name: 'Percy Lavon Julian', profession: 'chemist' },
     ];
     
     export default function List() {
        // chemistのみを抽出
        const chemists = people.filter(person =>
           person.profession === 'chemist'
        );
     
        // 抽出した結果をTSX要素に変換
        const listItems = chemists.map(person =>
           <li key={person.id}>{person.name}</li>
        );
     
        return <ul>{listItems}</ul>;
     }
    


    filter()map() はメソッドチェーンで記述することもできる。

     const listItems = people
        .filter(person => person.profession === 'chemist')
        .map(person => <li key={person.id}>{person.name}</li>);
    


    条件に応じて異なるセクションを表示するコンポーネントに抽出するパターンを以下に示す。

     interface ListSectionProps {
        title: string;
        people: Person[];
     }
     
     function ListSection({ title, people }: ListSectionProps) {
        return (
           <>
              <h2>{title}</h2>
              <ul>
                 {people.map(person =>
                    <li key={person.id}>{person.name}</li>
                 )}
              </ul>
           </>
        );
     }
     
     export default function App() {
        const chemists = people.filter(p => p.profession === 'chemist');
        const others = people.filter(p => p.profession !== 'chemist');
     
        return (
           <>
              <ListSection title="化学者" people={chemists} />
              <ListSection title="その他" people={others} />
           </>
        );
     }
    



    ネストしたリスト

    カテゴリとその配下のアイテムのように、入れ子になった配列構造をレンダリングする場合がある。
    ネストしたリストでは、外側と内側の map() それぞれに適切な key を設定する必要がある。

    入れ子の配列のレンダリング

    カテゴリとアイテムを持つネストしたリストのレンダリング例を以下に示す。

     interface Recipe {
        id: string;
        name: string;
        ingredients: string[];
     }
     
     const recipes: Recipe[] = [
        {
           id: 'greek-salad',
           name: 'Greek Salad',
           ingredients: ['tomatoes', 'cucumber', 'olives', 'feta']
        },
        {
           id: 'hummus',
           name: 'Hummus',
           ingredients: ['chickpeas', 'olive oil', 'garlic', 'lemon']
        },
        {
           id: 'pizza',
           name: 'Pizza',
           ingredients: ['pizza crust', 'pizza sauce', 'mozzarella']
        },
     ];
     
     export default function RecipeList() {
        return (
           <div>
              {recipes.map(recipe =>
                 <div key={recipe.id}>
                    <h2>{recipe.name}</h2>
                    <ul>
                       {recipe.ingredients.map(ingredient =>
                          <li key={ingredient}>{ingredient}</li>
                       )}
                    </ul>
                 </div>
              )}
           </div>
        );
     }
    


    ネストしたリストのポイントを以下に示す。

    • 外側の map() では recipe.idkey に使用する
      データベースIDを使用するため、最適な key の選択となる。

    • 内側の map() では ingredient (材料名の文字列) を key に使用する
      同一レシピ内での材料名は一意であるため、文字列を key として使用できる。

    • 各レベルで独立したkeyスコープが存在する
      外側リストと内側リストの key は独立しており、異なるレシピの内側リスト間で同じ材料名が使用されていても問題ない。


    また、複数の要素をラップしつつ余分なDOMノードを追加したくない場合は、Fragment を使用して key を指定できる。

     import { Fragment } from 'react';
     
     export default function RecipeList() {
        return (
           <div>
              {recipes.map(recipe =>
                 <Fragment key={recipe.id}>
                    <h2>{recipe.name}</h2>
                    <ul>
                       {recipe.ingredients.map(ingredient =>
                          <li key={ingredient}>{ingredient}</li>
                       )}
                    </ul>
                 </Fragment>
              )}
           </div>
        );
     }
    


    短縮構文 <> では key を指定できないため、明示的な Fragment コンポーネントを使用する必要がある。


    リストレンダリングの注意事項

    リストレンダリングを定義する場合は、空配列の処理とパフォーマンスに関する注意が必要である。

    空配列の場合の処理

    filter() の結果が空配列になる場合 や データが存在しない場合の処理を適切に記述することが重要である。

     interface Item {
        id: number;
        name: string;
     }
     
     interface FilteredListProps {
        items: Item[];
        query: string;
     }
     
     export default function FilteredList({ items, query }: FilteredListProps) {
        const filteredItems = items.filter(item =>
           item.name.includes(query)
        );
     
        return (
           <ul>
              {filteredItems.length > 0 ? (
                 filteredItems.map(item =>
                    <li key={item.id}>{item.name}</li>
                 )
              ) : (
                 <li>アイテムが見つかりません</li>
              )}
           </ul>
        );
     }
    


    下表に、空配列の処理に関するポイントを示す。

    空配列の処理に関する注意事項
    項目 説明
    length > 0 による条件分岐 配列が空かどうかをチェックして、空の場合はフォールバックUIを表示する。
    ユーザにとって何も表示されないよりも、"結果なし"のメッセージを表示する方が親切な設計となる。
    && 演算子による短絡評価は避ける {items.length && items.map(...)} のような記述では、items.lengthが0の場合、0がそのままレンダリングされるバグが生じる。
    そのため、三項演算子(? :)を使用することを推奨する。


    パフォーマンスの考慮

    通常のリストレンダリングは数百件程度のデータであれば問題なく動作する。
    しかし、数千件から数万件の大規模なリストになると、全DOMノードの生成と保持によりパフォーマンスが低下する場合がある。

    大規模リストに対しては仮想化 (Virtualization) の導入を検討する必要がある。
    仮想化とは、画面上に表示されている部分のみをDOMに描画し、スクロールに応じて描画範囲を動的に切り替える技術である。

    下表に、代表的な仮想化ライブラリを示す。

    Reactの仮想化ライブラリ
    ライブラリ 特徴 推奨用途
    react-window 軽量でシンプルなAPI
    バンドルサイズが小さい。
    アイテムの高さが固定の場合
    react-virtuoso 高機能で動的な高さに対応
    グループ化やフッタ等の機能も持つ。
    アイテムの高さが動的な場合、高度な機能が必要な場合


    仮想化ライブラリの使用を検討する目安を以下に示す。

    • 100件以下のリスト
      仮想化は不要
      map() によるレンダリングで問題ない。
    • 100件から1,000件程度のリスト
      ユーザの端末性能や表示内容の複雑さによって判断する。
    • 1,000件以上のリスト
      仮想化ライブラリの導入を強く推奨する。



    関連情報