React入門第9回~配列を表示してみよう~

Reactで配列を表示するには?

OZ
OZ

先生、ReactでWebサイトを作っているんですが、配列のデータを表示したいんです。どうすればいいですか?

プロ太
プロ太

具体例で説明していきましょう。例えば、好きなスポーツのリストを表示するプログラムを作ってみましょう!

const sports = ["サッカー", "野球", "バスケ"];

const SportsList = () => {
  return (
    <>
      <h3>人気のスポーツ</h3>
      <ul>
        <li>{sports[0]}</li>
        <li>{sports[1]}</li>
        <li>{sports[2]}</li>
      </ul>
    </>
  );
};

export default SportsList;

なるほど!配列の要素を一つずつ指定して表示しているんですね。でも、配列の要素が増えたら大変そうです…

その通りです!そこで、あまり使われないですが、for文を使って書く方法を見てみましょう。

const sports = ["サッカー", "野球", "バスケ"];

const SportsList = () => {
  const sportList = [];
  for (const sport of sports) {
    sportList.push(<li>{sport}</li>);
  }
  
  return (
    <>
      <h3>人気のスポーツ</h3>
      <ul>
        {sportList}
      </ul>
    </>
  );
};

おぉ!!なるほど!!for文を使うと、配列の要素が増えても対応できるんですね!でも、なぜこの書き方はあまり使われないんですか?

重要なポイントですね!実は、JSXには大事なルールがあります。JSXの中の{}では、文(statement)を書くことができないんです。このあたりは前の授業で扱っているので、忘れていたら復習しておきましょう!

なので、for文よりmap関数を使う方法がよく使われます。こんな感じです。

const sports = ["サッカー", "野球", "バスケ"];

const SportsList = () => {
  return (
    <>
      <h3>人気のスポーツ</h3>
      <ul>
        {sports.map((sport) => (
          <li>{sport}</li>
        ))}
      </ul>
    </>
  );
};

map関数を使うと、コードがすっきりしましたね!でも、map関数ってなんですか?

map関数は、配列の各要素に対して同じ処理を行い、新しい配列を作る関数です。この場合:

  1. sports配列の各要素(sport)に対して
  2. <li>{sport}</li>というJSXを作成
  3. それらを新しい配列にまとめて返す

という処理を行っています。シンプルで読みやすいコードになりますよね!

なるほど!でも、map関数の使い方がまだよくわかりません..

では、map関数の使い方の特徴を先に説明しますね。

map関数について

map 関数は、配列の各要素に対して指定した処理を行い、その結果を新しい配列として返すJavaScriptのメソッドです。元の配列は変更せず、新しい配列が生成されるため、安全にデータを操作できる便利な方法です。

基本的な構文

const newArray = array.map((element, index, array) => {
  // 要素ごとに行いたい処理
  return element * 2; // 例: 各要素を2倍にする
});

map 関数には、以下の3つの引数を取れるコールバック関数を渡せます。

  1. element: 現在処理している配列の要素
  2. index(省略可能): 現在の要素のインデックス
  3. array(省略可能): map メソッドが実行される元の配列

簡単な例

例えば、各要素を2倍にした新しい配列を作りたい場合は次のようにします。

const numbers = [1, 2, 3, 4];
const doubled = numbers.map((num) => num * 2);
console.log(doubled); // [2, 4, 6, 8]

ここでは、num * 2 の計算結果が新しい配列の各要素となり、doubled[2, 4, 6, 8] となります。

実用的な例

配列内のオブジェクトに対しても map を使うことができます。例えば、ユーザーの名前を大文字に変換して取得したいとします。

const users = [
  { name: 'Alice', age: 25 },
  { name: 'Bob', age: 30 },
  { name: 'Charlie', age: 35 }
];

const upperCaseNames = users.map((user) => user.name.toUpperCase());
console.log(upperCaseNames); // ['ALICE', 'BOB', 'CHARLIE']

この例では、users 配列の各オブジェクトから name プロパティを取得し、大文字に変換した結果を含む新しい配列が upperCaseNames に格納されます。

map関数の特徴
  • 非破壊的: map は元の配列を変更せずに、新しい配列を返します。
  • チェーン可能: 他のメソッドと組み合わせて、複数の処理を連続して行えます(例えば、filtermap を組み合わせるなど)。
注意点

map は必ず配列と同じ長さの新しい配列を返すため、条件によって要素数を変えたい場合は filter メソッドと組み合わせて使うのが一般的です。

filterとの違い
  • map は全ての要素を処理し、新しい配列を生成する。
  • filter は条件に一致した要素のみを含む新しい配列を返す。

このように、map 関数を使うと配列を簡単に加工でき、さまざまな場面で役立ちます。

なるほど!!詳しい説明ありがとうございます!!せっかくなので、filter関数の復習もしておきたいです!!

filter関数について

filter 関数は、配列から特定の条件に合う要素を選び出して新しい配列を返すメソッドです。元の配列は変更されず、条件に合った要素だけを含む新しい配列が返されます。

基本的な使い方

const newArray = array.filter((element) => {
  // 条件に合う場合のみ `true` を返す
  return element < 10;  // 例: 10未満の要素を抽出
});

この例では、array の中から、10未満の要素だけを抽出して newArray に格納しています。

filter 関数の構文
const newArray = array.filter((element, index, array) => {
  // 条件式(真偽値を返す)
  return condition;
});
  • element: 配列の現在の要素
  • index: 配列の現在の要素のインデックス(位置)
  • array: 配列そのもの(オプションで使用)

filter は条件式が true を返す要素だけを新しい配列に追加し、false を返す要素は除外します。


例1: 数字の配列から偶数だけを抽出

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const evenNumbers = numbers.filter((number) => number % 2 === 0);
console.log(evenNumbers); // [2, 4, 6, 8]

この例では、配列 numbers から 偶数だけを抽出しています。条件式 number % 2 === 0true である偶数の要素だけが新しい配列 evenNumbers に入ります。

例2: オブジェクトの配列から特定の条件でフィルタリング

const foods = [
  { id: 1, name: "ハンバーガー", price: 800 },
  { id: 2, name: "ステーキ", price: 1500 },
  { id: 3, name: "サラダ", price: 600 }
];

const cheapFoods = foods.filter((food) => food.price <= 1000);
console.log(cheapFoods);
// [
//   { id: 1, name: "ハンバーガー", price: 800 },
//   { id: 3, name: "サラダ", price: 600 }
// ]

この例では、foods 配列から 価格が1000円以下 の食べ物を抽出しています。food.price <= 1000 の条件を満たすオブジェクトだけが新しい配列 cheapFoods に入ります。

filterメソッドまとめ
  • filter元の配列を変更せず、条件に一致する要素だけを抽出して新しい配列を返します。
  • 使い方は簡単で、filter に渡すコールバック関数で条件を指定するだけです。
  • 配列の中身を条件に応じて絞り込みたい場合に便利なメソッドです。

バッチリ分かりました!!復習ありがとうございます!!

では、それを踏まえた上で下のReactのコードを見てみましょう!!

const foods = [
  { id: 1, name: "ラーメン", price: 800 },
  { id: 2, name: "カレー", price: 750 },
  { id: 3, name: "うどん", price: 600 }
];

const FoodList = () => {
  return (
    <>
      <h3>メニューリスト</h3>
      <ul>
        {foods.map((food) => (
          <li key={food.id}>
            {food.name}は{food.price}円です
          </li>
        ))}
      </ul>
    </>
  );
};

map関数は3つの部分で構成されています

  1. 配列.map() – 配列に対してmap関数を呼び出します
  2. ((food) => – 配列の各要素を表す引数を第1引数に指定します
  3. <li>...</li>) – 各要素をどのようにして表示するかを書きます

わかりやすいです!ということは、配列の要素に対して何か処理をしたい場合も、map関数の中で書けるんですね?

その通りです!例えば、1000円以下のメニューだけに「お買い得」と表示する例を見てみましょう!

const foods = [
  { id: 1, name: "ハンバーガー", price: 800 },
  { id: 2, name: "ステーキ", price: 1500 },
  { id: 3, name: "サラダ", price: 600 },
];

{foods.map((food) => (
  <li key={food.id}>
    {food.name}は{food.price}円です
    {food.price <= 1000 && <span style={{color: 'red'}}> お買い得!</span>}
  </li>
))}

{条件 && 表示するJSX} の形を使うと、条件が true の場合にのみ JSX がレンダリングされます。
このコードで新しく作成される配列は次のようになります。

[
  <li key="1">ハンバーガーは800円です <span style={{color: 'red'}}> お買い得!</span></li>,
  <li key="2">ステーキは1500円です</li>,
  <li key="3">サラダは600円です <span style={{color: 'red'}}> お買い得!</span></li>
]

これをJSXの{}で囲っているので、この配列が展開されて表示される仕組みです。

なるほど!!分かりました!!ありがとうございます!!
でも、先生!1つ気になるんですが、このコードで<li>タグにkeyってついてるんですけど、これって何のために必要なんですか?

keyが必要な理由

よい質問です!今後、Reactでは、リストタグには、必ずキーを設定してください!まず、なぜつけるかを説明するために、Reactの動作の特徴を説明します。Reactは画面を更新する時、前の状態と新しい状態を比較して、変更があった部分だけを更新しようとします。これを『差分検出』と呼びます。そして、keyがないと困ることが起こるんです。簡単な例で説明しましょう!!

const TodoList = () => {
  const [todos, setTodos] = useState([
    "数学の宿題",
    "買い物",
    "部活"
  ]);

  const addTodo = () => {
    // リストの先頭に「新しいTodo」を追加
    setTodos(["新しいTodo", ...todos]);
  };

  return (
    <ul>
      {/* ❌ keyなしの場合 */}
      {todos.map((todo) => (
        <li>{todo}</li>
      )}
    </ul>
  );
};

このコードには問題があります。新しい項目を追加すると、Reactは全ての<li>要素を作り直してしまいます。

え?なんでですか?

Reactは先頭から順に要素を比較していきます。keyがないと、『1番目は違う内容になった、2番目も違う…』と判断して、全部作り直してしまうんです。

でも、keyをつけると…

// ✅ 修正後 keyありの場合
const [todos, setTodos] = useState([
  { id: 1, text: "数学の宿題" },
  { id: 2, text: "買い物" },
  { id: 3, text: "部活" }
]);

return (
  <ul>
    {todos.map((todo) => (
      <li key={todo.id}>{todo.text}</li>
    ))}
  </ul>

こうすると、Reactは『あ、これは新しい要素が追加されただけで、他の要素は同じものだ!』とわかるんです。

なるほど!こうすると、必要な部分だけ更新できるんですね。

keyをつける時の注意点

ただし、keyをつける時には3つの重要なルールがあります。

1.リスト内の全ての要素に重複しない名前をつける必要がある
 →※1つの親からぶら下がった子同士の名前が重複しなければOK

const students = [
  { id: 1, name: "田中" },
  { id: 2, name: "鈴木" },
  { id: 2, name: "佐藤" }  // ❌ 同じidは使えません!
];

ただし、同じ値のkeyが他のリストにあっても問題ありません。同じ親要素の中で一意であれば大丈夫です。これも下の例を見ておきましょう!

const SchoolRoster = () => {
  const classA = [
    { number: 1, name: "田中" },
    { number: 2, name: "鈴木" },
    { number: 3, name: "佐藤" }
  ];

  const classB = [
    { number: 1, name: "山田" },
    { number: 2, name: "伊藤" },
    { number: 3, name: "渡辺" }
  ];

  return (
    <div>
      <div>
        <h3>1年A組</h3>
        <ul>
          {classA.map((student) => (
            <li key={student.number}>{student.name}</li>
          )}
        </ul>
      </div>
      
      <div>
        <h3>1年B組</h3>
        <ul>
          {classB.map((student) => (
            <li key={student.number}>{student.name}</li>
          )}
        </ul>
      </div>
    </div>
  );
};

あれ?A組もB組も出席番号1から3まで同じ番号を使っていますけど、大丈夫なんですか?

はい、これは問題ありません!その理由は、それぞれのmapが異なる<ul>の中で実行されているからです。

つまり:

  • A組の<li>要素は、A組の<ul>の中でのみ一意であればOK
  • B組の<li>要素は、B組の<ul>の中でのみ一意であればOK

これはツリー構造で考えると分かりやすいです。

div
├── ul(A組)
│   ├── li (key=1) 田中さん
│   └── li (key=2) 鈴木さん
└── ul(B組)
    ├── li (key=1) 山田さん  // 別の親要素なのでOK
    └── li (key=2) 伊藤さん  // 別の親要素なのでOK

2.キーは変更してはいけません

// ❌ 良くない例
<li key={Math.random()}>{todo}</li>

// ✅ 良い例
<li key={todo.id}>{todo}</li>

3.配列のインデックスはなるべく使わない

// ❌ あまり良くない例
{todos.map((todo, index) => (
  <li key={index}>{todo}</li>
))}

// ✅ 良い例
{todos.map(todo => (
  <li key={todo.id}>{todo}</li>
))}

インデックスを使うと、配列の要素を並べ替えたり、途中に挿入したりした時に、同じ内容なのにkeyが変わってしまうんです。

できるだけ、各項目を一意に特定できるID(データベースのID、商品コードなど)をkeyとして使うことをお勧めします!

なるほど!!分かりました!!今日も勉強になりました!!ありがとうございます!

map関数補足:配列を返すのに、どうして表示される?

私がmap関数をReactで最初学習したときに出た疑問ですが、map関数は、新しい配列を返す関数なのだから、「配列が返されてるだけだから、表示されないのでは?」と思っていました。

この疑問に関しては、JavaScriptのmap関数は確かに新しい配列を返しますが、JSXの中でmap関数を使用する場合、Reactの特別な処理として、JSX内では、配列を展開して処理します。つまり、下記のようなコードを書いたとすると、

 {[<li>サッカー</li>, <li>野球</li>, <li>バスケ</li>]}

自動的に以下のように解釈されます。

 <li>サッカー</li>
 <li>野球</li>
 <li>バスケ</li>

このReactの特性を押させておきましょう。

まとめ

Reactで配列のデータを表示する際には、map関数を使うと便利です。map関数を使うことで、コードをすっきりと記述でき、配列の要素が増えたり減ったりしても簡単に対応できます。また、Reactではリスト要素にkeyを付けることが重要です。keyは、リストの更新を効率よく行うために必要で、特に重複しない一意な値を使うことが推奨されます。この記事では、keyの役割やmap関数の使い方について具体例を交えて解説しました。Reactのリスト表示で迷わないように、ぜひ参考にしてください!