React.js で map() を使ってみる

古くなったIT技術にはご注意ください

リストとキー

まず、JavaScriptでリストをどのように変換するかを見てみましょう。以下のコードでは、map()関数を使用して数値の配列をとり、それらの値を2倍にします。 map()によって返された新しい配列を変数doubledに代入してログに記録します

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
console.log(doubled);

このコードは [2,4,6,8,10] をコンソールに記録します。

複数のコンポーネントのレンダリング

要素のコレクションを作成し、中括弧{}を使用してJSXに含めることができます。以下では、Javascriptのmap()関数を使用してnumbers配列をループします。各アイテムに

要素を返します。最後に、結果の要素の配列をlistItemsに割り当てます。

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  <li>{number}</li>
);

listItems配列全体を<ul>要素の中に含め、それをDOMにレンダリングします。

ReactDOM.render(
  <ul>{listItems}</ul>,
  document.getElementById('root')
);

CodePenで試してみてください。

このコードは1から5までの数字の箇条書きリストを表示します。

基本リストコンポーネント

通常は、コンポーネントをリスト内にレンダリングします。数値の配列を受け取り、順序付けられていない要素のリストを出力するコンポーネントに、リファクタリングすることができます。

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <li>{number}</li>
  );
  return (
    <ul>{listItems}</ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

このコードを実行すると、リスト項目にキーを提供する必要があるという警告が表示されます。“キー”は、要素のリストを作成するときに含める必要がある特別な文字列属性です。次のセクションで重要な理由について説明します。numbers.map()`内のリスト項目にキーを割り当て、欠落しているキーの問題を修正しましょう。

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>

    //ここ
    <li key={number.toString()}>
      {number}
    </li>
  );
  return (
    <ul>{listItems}</ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

CodePenで試してみてください。

Keys

キーは、どのアイテムが変更されたのか、追加されたのか、削除されたのかを識別します。要素に安定したアイデンティティを与えるために、配列内の要素にキーを与える必要があります。

const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
  //ここ
  <li key={number.toString()}>
    {number}
  </li>
);

キーを選択する最も良い方法は、兄弟間でリスト項目を一意に識別する文字列を使用することです。多くの場合、データのIDをキーとして使用します。

const todoItems = todos.map((todo) =>
  //ここ
  <li key={todo.id}>
    {todo.text}
  </li>
);

レンダリングされたアイテムのIDが安定していない場合は、アイテムインデックスを最後の手段としてキーとして使用できます。

const todoItems = todos.map((todo, index) =>
  // アイテムに安定したIDがない場合にのみ行う
  <li key={index}>
    {todo.text}
  </li>
);

項目の並べ替えが遅い場合、キーのインデックスを使用することはお勧めしません。あなたが興味を持っている場合、なぜキーが必要なのかについての詳細な説明を読むことができます。

キーによるコンポーネントの抽出

キーは、周囲の配列のコンテキストでのみ意味があります。

たとえば、ListItemコンポーネントを抽出する場合は、ListItem自体のルート

要素ではなく、配列の要素にキーを保持する必要があります。

例:不正なキーの使用

function ListItem(props) {
  const value = props.value;
  return (
    // 違う!ここでキーを指定する必要はありません。
    <li key={value.toString()}>
      {value}
    </li>
  );
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // 違う!キーはここで指定されているはずです:
    <ListItem value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

例:正しいキーの使用法

function ListItem(props) {
  // 正しい!ここでキーを指定する必要はありません。
  return <li>{props.value}</li>;
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // 正しい!キーは配列内で指定する必要があります。
    <ListItem key={number.toString()}
              value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

CodePenで試してみてください。

経験則として、map()コールの中の要素にはキーが必要です。

キーは唯一の兄弟でなければならない

配列内で使用されるキーは、兄弟間で一意でなければなりません。しかし、それらは世界的にユニークである必要はありません。 2つの異なる配列を生成する場合、同じキーを使用できます。

function Blog(props) {
  const sidebar = (
    <ul>
      {props.posts.map((post) =>
        <li key={post.id}>
          {post.title}
        </li>
      )}
    </ul>
  );
  const content = props.posts.map((post) =>
    <div key={post.id}>
      <h3>{post.title}</h3>
      <p>{post.content}</p>
    </div>
  );
  return (
    <div>
      {sidebar}
      <hr />
      {content}
    </div>
  );
}

const posts = [
  {id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
  {id: 2, title: 'Installation', content: 'You can install React from npm.'}
];
ReactDOM.render(
  <Blog posts={posts} />,
  document.getElementById('root')
);

CodePenで試してみてください。

キーはReactへのヒントとして機能しますが、コンポーネントに渡されることはありません。コンポーネントに同じ値が必要な場合は、別の名前のプロップとして明示的に渡します。

const content = posts.map((post) =>
  <Post
    key={post.id}
    id={post.id}
    title={post.title} />
);

上記の例では、Postコンポーネントはprops.idを読み取ることができますが、props.keyは読み取ることができません。

JSXにmap()を埋め込む

上記の例では、別のlistItems変数を宣言し、JSXにそれを含めました:

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <ListItem key={number.toString()}
              value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

JSXでは、任意の式を中括弧で埋め込むことができるので、map()の結果をインライン展開できます。

function NumberList(props) {
  const numbers = props.numbers;
  return (
    <ul>
      {numbers.map((number) =>
        <ListItem key={number.toString()}
                  value={number} />
      )}
    </ul>
  );
}
[CodePen](https://codepen.io/gaearon/pen/BLvYrB?editors=0010}で試してみてください。

場合によってはこれによりコードがより明確になることもありますが、このスタイルも悪用される可能性があります。 JavaScriptのように、可読性のために変数を抽出する価値があるかどうかを判断するのは、あなた次第です。 map()のボディがネストすぎると、コンポーネントを抽出するのがよいことに注意してください。

参考

Lists and Keys

藤沢瞭介(Ryosuke Hujisawa)
  • りょすけと申します。18歳からプログラミングをはじめ、今はフロントエンドでReactを書いたり、AIの勉強を頑張っています。off.tokyoでは、ハイテクやガジェット、それからプログラミングに関する情報まで、エンジニアに役立つ情報を日々発信しています!

未整理記事