【React】map()でJSX要素を繰り返し処理!key属性の使い方などをわかりやすく解説!

ReactでUIを実装していると「配列の内容をリスト化して表示させたい」といった場面が良くあります。

そんなときに活躍するのが、JavaScriptのmap()関数です。

この記事では、Reactで『JSX要素を繰り返し処理する方法』について、以下の内容をサンプルコードを用いてわかりやすく解説します。

  • map()でJSX要素を繰り返し処理をする方法
    • key属性を設定する
    • key属性にindexを使う場合
    • key属性にindexを使った場合に正しく再描画されない例
    • map()で複数の要素を返す場合
    • map()returnを省略できる条件

map()でJSX要素を繰り返し処理をする方法

JavaScriptのmap()関数は、配列の各要素に対して処理を行い、新しい配列を返す関数です。

Reactではこのmap()関数を使って、JSX要素を繰り返し描画します。以下にサンプルコードを示します。

const users = ['佐藤', '鈴木', '高橋'];

function App() {
  return (
    <ul>
      {users.map((user) => (
        <li>{user}</li>
      ))}
    </ul>
  );
}

export default App;

上記のサンプルコードでは、usersという配列の中身を、<li>タグで順番に表示しています。

Reactには、繰り返し処理や条件分岐といった制御構文のための特別な構文は用意されていません。その代わり、JavaScriptの標準的な構文(mapや三項演算子など)をそのまま使って記述します。そのため、JavaScriptに慣れている人にとっては、Reactの記述も直感的に理解しやすいという特徴があります。

key属性を設定する

冒頭に示したサンプルコードでもコードは動作しますが、開発者ツールを確認すると、以下の警告が出ています。

Warning: Each child in a list should have a unique "key" prop.
↓
↓ 翻訳
↓
警告:リスト内の各子要素には、一意な "key" 属性を持たせる必要があります。

keyは、Reactが「どの要素が変わったか」を判断するための目印です。リストを動的にレンダリングする場合には、必ずkeyを設定する必要があります。サンプルコードを以下に示します。

const users = ['佐藤', '鈴木', '高橋'];

function App() {
  return (
    <ul>
      {users.map((user) => (
        <li key={user}>{user}</li>
      ))}
    </ul>
  );
}

export default App;

このように、要素自体が一意であれば、それをkeyにしてOKです。

オブジェクトに一意なIDがある場合は、keyにそのIDを指定するのがベストプラクティスです。サンプルコードを以下に示します。

const users = [
  { id: 1, name: '佐藤' },
  { id: 2, name: '鈴木' },
  { id: 3, name: '高橋' },
];

function App() {
  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

export default App;

key属性にindexを使う場合

配列のインデックス(index)をkeyとして使うこともできます。

{users.map((user, index) => (
  <li key={index}>{user}</li>
))}

ただし、インデックスをkeyにすると、要素の追加・削除・並び替えの際に正しく再描画されない可能性があります。

そのため、一意なID(ユニークID)をkeyに使うのがベストです。ユーザーが要素を追加・削除・並び替えできるようなリストでは、indexの使用は避けましょう。

key属性にindexを使った場合に正しく再描画されない例

インデックスをkeyにしたことで意図しない再描画が起きるサンプルコードで紹介します。以下のサンプルコードでは、配列のインデックス(index)をkeyにした結果、要素の内容は変わったのに、Reactが同じ要素と誤認識して再描画されない現象が発生します。

import { useState } from 'react';

function App() {
  const [users, setUsers] = useState([{ name: '佐藤' }, { name: '鈴木' }, { name: '高橋' }]);

  const handleDeleteFirst = () => {
    // 先頭の要素を削除
    setUsers(users.slice(1));
  };

  return (
    <div>
      <button onClick={handleDeleteFirst}>先頭のユーザーを削除</button>
      <ul>
        {users.map((user, index) => (
          <li key={index}>
            <div>name:{user.name}</div>
            <input defaultValue={user.name} />
          </li>
        ))}
      </ul>
    </div>
  );
}

export default App;

「先頭のユーザーを削除」ボタンをクリックして、先頭のユーザーを削除しても、残りの要素のkey(=index)は変わりません。そのため、Reactは「key=0は前にもあったから同じ要素だな。じゃあ再生成せずに再利用しよう!」と誤判断して再レンダリングせずに再利用してします。上記のコードは以下のように動作します。

初期状態
key: 0   name: 佐藤   [input: 佐藤]
key: 1   name: 鈴木   [input: 鈴木]
key: 2   name: 高橋   [input: 高橋]
↓
↓「先頭のユーザーを削除」ボタンをクリック
↓
key: 0   name: 鈴木   [input: 佐藤]
key: 1   name: 高橋   [input: 鈴木]

上記のようになり、name表示は更新されるが、inputの中身が更新されません。

<li key={0}> という要素は初期状態では"佐藤"を表示していました。その後「先頭の要素(佐藤)」を削除しても、Reactはkey=0の要素が次のレンダリングでも存在するため、「同じ要素」と判断して再利用します。ただし、<li key={0}>内の<div>の中身は先頭の要素が削除されたことでuser.name"鈴木"に変わったので、Reactはこのテキストを正しく差し替えます。しかし、<input />は非制御(uncontrolled)コンポーネントなので、再利用されたままです。その結果、<div> に表示されている名前は"鈴木"になっているのに、inputの中身は"佐藤"のままになってしまい、表示にズレが生じるのです。

map()で複数の要素を返す場合

map()で繰り返し処理をするとき、複数の要素(例えば<h2><p>など)を返す場合もありますよね。そのときは、1つの親要素でラップする必要があります。ReactのJSXでは、複数の要素を1つの親要素で包まずに返すと構文エラー(文法エラー)になります。

{users.map((user) => (
  <h2>{user.id}</h2>
  <p>{user.name}</p> // ← これは構文エラー
))}

解決策①:<div><li>などのタグでラップ

<div>などのHTMLタグで複数の要素を1つの親要素としてラップすることで、構文エラーを回避できます。<div>で囲むことで、<h2><p>の2つの要素をまとめて1つの要素として扱えるため、Reactのルールに従った正しいJSXになります

{users.map((user) => (
  <div key={user.id}>
    <h2>{user.id}</h2>
    <p>{user.name}</p>
  </div>
))}

解決策②:<React.Fragment>(<>〜</>)でラップ

HTMLタグを増やしたくない場合は、<React.Fragment>を使うと便利です。

{users.map((user) => (
  <React.Fragment key={user.id}>
    <h2>{user.id}</h2>
    <p>{user.name}</p>
  </React.Fragment>
))}

省略記法の場合には、以下のようになります。

{users.map((user) => (
  <>
    <h2>{user.id}</h2>
    <p>{user.name}</p>
  </>
))}

ただし、省略記法の<>にはkey属性を付けられないので、map()で使うと警告がでます。そのため、map()内でkey属性を使いたい場合は<React.Fragment key={...}>を使うようにしましょう。

map()でreturnを省略できる条件

map()を使うとき、アロー関数の書き方によってreturnを省略できる場合があります。

具体的には、アロー関数の本体が1つの式だけの場合、その式の結果が暗黙的に返されるため、returnを省略することができます。

たとえば、以下のように()(丸かっこ)で囲んでJSXをそのまま返す場合は、returnを省略できます。

{users.map((user) => (
  <li key={user}>{user}</li>
))}

この記事で紹介してきた書き方はすべてこの「省略記法」です。

一方、関数の中で複数の処理(例: console.log()など)を行いたい場は、波かっこ({})を使う必要があります。このときは、明示的に returnを書かなければなりません。

{users.map((user) => {
  console.log(user);  // 他の処理を追加
  return <li key={user}>{user}</li>;
})}
書き方returnの記述条件
() => (...)省略できる本体が1つの式で、丸かっこで囲む場合
() => { ... }必要本体に複数の処理がある/ブロックで書く場合

本記事のまとめ

この記事では、Reactで『JSX要素を繰り返し処理する方法』について、以下の内容を説明しました。

  • map()でJSX要素を繰り返し処理をする方法
    • key属性を設定する
    • key属性にindexを使う場合
    • key属性にindexを使った場合に正しく再描画されない例
    • map()で複数の要素を返す場合
    • map()returnを省略できる条件

お読み頂きありがとうございました。

スポンサーリンク