リストのレンダー
データの集まりから、似たようなコンポーネントを複数表示させたいことがあります。データの配列を操作するには JavaScript の配列メソッドが使えます。このページでは filter()
や map()
を React で使用して、データの配列をフィルタリングしたり、コンポーネントの配列に変換したりする方法を見ていきましょう。
このページで学ぶこと
- JavaScript の
map()
を使用して、配列からコンポーネントをレンダーする方法 - JavaScript の
filter()
を使用して、特定のコンポーネントのみをレンダーする方法 - React での key の使用方法と、その必要性
配列からデータをレンダー
以下のようなコンテンツのリストがあるとしましょう。
<ul>
<li>Creola Katherine Johnson: mathematician</li>
<li>Mario José Molina-Pasquel Henríquez: chemist</li>
<li>Mohammad Abdus Salam: physicist</li>
<li>Percy Lavon Julian: chemist</li>
<li>Subrahmanyan Chandrasekhar: astrophysicist</li>
</ul>
これらのリスト項目は、その中身、すなわちデータのみが異なっています。インターフェースを構築する際にはよく、コメント一覧やプロフィール画像のギャラリなどのように、異なるデータを使用して同じコンポーネントの複数のインスタンスを表示する必要があります。このような場合、JavaScript のオブジェクトや配列にそのデータを保存し、map()
や filter()
などのメソッドを使ってコンポーネントのリストをレンダーすることができます。
以下は、配列からアイテムのリストを生成する方法を示す簡単な例です。
- データを配列に移動します。
const people = [
'Creola Katherine Johnson: mathematician',
'Mario José Molina-Pasquel Henríquez: chemist',
'Mohammad Abdus Salam: physicist',
'Percy Lavon Julian: chemist',
'Subrahmanyan Chandrasekhar: astrophysicist'
];
people
内のメンバをlistItems
という新しい JSX の配列にマップします。
const listItems = people.map(person => <li>{person}</li>);
- コンポーネントから
listItems
を<ul>
で囲んで返します。
return <ul>{listItems}</ul>;
以下が結果です。
const people = [ 'Creola Katherine Johnson: mathematician', 'Mario José Molina-Pasquel Henríquez: chemist', 'Mohammad Abdus Salam: physicist', 'Percy Lavon Julian: chemist', 'Subrahmanyan Chandrasekhar: astrophysicist' ]; export default function List() { const listItems = people.map(person => <li>{person}</li> ); return <ul>{listItems}</ul>; }
上のサンドボックスには、以下のようなコンソールエラーが表示されています。
このエラーを修正する方法についてはこのページの後半で説明します。その前に、このデータに構造を追加しましょう。
アイテムの配列をフィルタする
このデータにさらに構造を加えてみました。
const people = [{
id: 0,
name: 'Creola Katherine Johnson',
profession: 'mathematician',
}, {
id: 1,
name: 'Mario José Molina-Pasquel Henríquez',
profession: 'chemist',
}, {
id: 2,
name: 'Mohammad Abdus Salam',
profession: 'physicist',
}, {
id: 3,
name: 'Percy Lavon Julian',
profession: 'chemist',
}, {
id: 4,
name: 'Subrahmanyan Chandrasekhar',
profession: 'astrophysicist',
}];
ここで、職業 (profession) が 'chemist'
の人だけを表示したいとしましょう。JavaScript の filter()
メソッドを使用すれば、そのような職業の人だけを返すことができます。このメソッドは、要素の配列を受け取り、個々の要素を「テスト」(true
または false
を返す関数)にかけ、そして、テストを通過した要素(テスト関数が true
を返したもの)のみを含む配列を返します。
profession
が 'chemist'
となっている要素のみが必要でした。そのための「テスト」関数は、(person) => person.profession === 'chemist'
のようになります。使い方の全体像は以下のようになります。
people
に対してfilter()
を呼び出し、person.profession === 'chemist'
を使ってフィルタした、職業が “chemist” である人物のみの新しい配列を作成します。
const chemists = people.filter(person =>
person.profession === 'chemist'
);
- 次に
chemists
に対して map を適用します。
const listItems = chemists.map(person =>
<li>
<img
src={getImageUrl(person)}
alt={person.name}
/>
<p>
<b>{person.name}:</b>
{' ' + person.profession + ' '}
known for {person.accomplishment}
</p>
</li>
);
- 最後に、コンポーネントから
listItems
を返します。
return <ul>{listItems}</ul>;
import { people } from './data.js'; import { getImageUrl } from './utils.js'; export default function List() { const chemists = people.filter(person => person.profession === 'chemist' ); const listItems = chemists.map(person => <li> <img src={getImageUrl(person)} alt={person.name} /> <p> <b>{person.name}:</b> {' ' + person.profession + ' '} known for {person.accomplishment} </p> </li> ); return <ul>{listItems}</ul>; }
key
によるリストアイテムの順序の保持
上記のすべてのサンドボックスで、コンソールにエラーが表示されていることに注目しましょう:
配列の各アイテムには、key
を渡す必要があります。配列内の他のアイテムと区別できるようにするための一意な文字列ないし数値のことです。
<li key={person.id}>...</li>
key は、配列のどの要素がどのコンポーネントに対応するのかを React が判断し、後で正しく更新するために必要です。これが重要となるのは、配列の要素が移動(ソートなどによって)した場合、挿入された場合、あるいは削除された場合です。適切に key
を選ぶことで、React は何が起こったか推測し、DOM ツリーに正しい更新を反映させることができます。
key は動的に生成するのではなく、元データに含めるべきです。
export const people = [{ id: 0, // Used in JSX as a key name: 'Creola Katherine Johnson', profession: 'mathematician', accomplishment: 'spaceflight calculations', imageId: 'MK3eW3A' }, { id: 1, // Used in JSX as a key name: 'Mario José Molina-Pasquel Henríquez', profession: 'chemist', accomplishment: 'discovery of Arctic ozone hole', imageId: 'mynHUSa' }, { id: 2, // Used in JSX as a key name: 'Mohammad Abdus Salam', profession: 'physicist', accomplishment: 'electromagnetism theory', imageId: 'bE7W1ji' }, { id: 3, // Used in JSX as a key name: 'Percy Lavon Julian', profession: 'chemist', accomplishment: 'pioneering cortisone drugs, steroids and birth control pills', imageId: 'IOjWm71' }, { id: 4, // Used in JSX as a key name: 'Subrahmanyan Chandrasekhar', profession: 'astrophysicist', accomplishment: 'white dwarf star mass calculations', imageId: 'lrWQx8l' }];
さらに深く知る
各アイテムが 1 つの DOM ノードではなく、複数の DOM ノードをレンダーする必要がある場合はどうするのでしょうか?
短い <>...</>
フラグメント構文では key
を渡せないため、これらを 1 つの <div>
にグループ化するか、やや長くてより明示的な <Fragment>
構文を使用する必要があります。
import { Fragment } from 'react';
// ...
const listItems = people.map(person =>
<Fragment key={person.id}>
<h1>{person.name}</h1>
<p>{person.bio}</p>
</Fragment>
);
フラグメントは DOM から消え去るため、これにより <h1>
、<p>
、<h1>
、<p>
というように続くフラットなリストが生成されます。
key
をどこから得るのか
データソースの種類によって key を得る方法は異なります。
- データベースからのデータ: データがデータベースから来る場合、データベースのキーや ID は必然的に一意ですので、それを利用できます。
- ローカルで生成されたデータ: データがローカルで生成されて保持される場合(例:ノートを取るアプリにおけるノート)は、アイテムを作成する際に、インクリメンタルなカウンタや
crypto.randomUUID()
、またはuuid
などのパッケージを使用します。
key
のルール
- キーは兄弟間で一意でなければなりません。ただし、異なる配列に対応する JSX ノードには同じキーを使用することができます。
- キーは変更してはいけません。さもないと
key
の目的が台無しになります。レンダーの最中に key を生成してはいけません。
なぜ React は key
を必要とするのか
デスクトップ上のファイルに名前がない場合を想像してください。代わりに、最初のファイル、2 番目のファイルといったように、順番によってそれらを区別する必要があるとしましょう。そのうち番号に慣れるかもしれませんが、ファイルを削除した途端に混乱してしまいますね。2 番目のファイルが 1 番目のファイルになり、3 番目のファイルが 2 番目のファイルになり、という具合です。
フォルダ内のファイル名と JSX の key の目的は似ています。兄弟間で項目を一意に識別できるようにするのです。適切に選択された key は、配列内の位置よりも多くの情報を提供します。並べ替えによって位置が変更されたとしても、key
のおかげで React はその項目が存在する限り、それを一意に識別できるのです。
まとめ
このページでは以下のことを学びました。
- コンポーネントからデータを配列やオブジェクトといったデータ構造に移動する方法。
- JavaScript の
map()
を使用して類似したコンポーネントの集まりを作成する方法。 - JavaScript の
filter()
を使用してフィルタリングされたアイテムの配列を作成する方法。 - コレクション内の各コンポーネントに
key
を設定して、位置やデータが変更された場合でも React がそれぞれを追跡できるようにする方法と、それが必要な理由。
チャレンジ 1/4: リストを 2 つに分割
この例では、すべての人物の一覧を表示しています。
それを、化学者 (chemist) と その他の人 の 2 つの別々の一覧に変更してください。前に述べたように、person.profession === 'chemist'
であるかどうかを確認することで、その人が化学者であるかどうかを決定できます。
import { people } from './data.js'; import { getImageUrl } from './utils.js'; export default function List() { const listItems = people.map(person => <li key={person.id}> <img src={getImageUrl(person)} alt={person.name} /> <p> <b>{person.name}:</b> {' ' + person.profession + ' '} known for {person.accomplishment} </p> </li> ); return ( <article> <h1>Scientists</h1> <ul>{listItems}</ul> </article> ); }