09. Lists and Keys - accgetter/React GitHub Wiki

最初にjavascriptでlistsをどのように作るか見てみましょう。
下のコードで、数字の配列と、その値を取る為に、map()ファンクションを使います。
doubleの変数をログ出力する為にmap()ファンクションで新しい配列をアサインします:

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

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

Reactでは、配列を要素のリストに変形することはほぼ同じです。

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

要素のコレクションを作って、JSX内の{}に含めることができます。
以下は、Javascriptのmap()を使って数値の配列をループさせます。
<li>タグを都度返却します。
最終的に、結果として要素の配列をlistItem変数にアサインします:

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

listItemそのものを<ul>タグの中に入れDOMに描画します:

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

Try it on 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')
);

このコードを実行するとき、keyが提供されるべきとのwarningが出ます。
key とは、要素のリストを作る際に必要な特別な属性です。
なぜそれが重要なのか次のセクションで議論しましょう。
さあ、keyをnumbers.map()の中のlist itemにkeyを設定し、この問題をfixさせましょう。

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')
);

Keys

KeyはReactが追加なり、削除なり、変更されたitemを認識するために必要です。
Keyは要素に安定的なidを持たせるために、配列の中で要素に付与すべきです。

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

keyを生成する一番いい方法は、リストアイテムの中にある、
ユニークな文字列を使うことです。ほとんどの場合、データのkeyをIDとして使いたいと思います。

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

安定的なIDがない場合、itemのindexを頼みの綱として使うこともあるでしょう。

const todoItems = todos.map((todo, index) =>
  // Only do this if items have no stable IDs
  <li key={index}>
    {todo.text}
  </li>
);

もし、itemが並べ替えできる場合は、indexを使うことはやめたほうがよいです。遅くなります。
興味があるなら を読んでみてください。

key付きのコンポーネントの抽出

Keyは配列周りの文脈でのみ意味をなすものです。
例えば、もしListItemコンポーネントを抽出するとき、
ListItem自身の中のrootである<li>要素よりも配列の中の要素の上でkeyを保つべき

例: Keyの間違った使い方

function ListItem(props) {
  const value = props.value;
  return (
    // Wrong! There is no need to specify the key here:
    <li key={value.toString()}>
      {value}
    </li>
  );
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // Wrong! The key should have been specified here:
    <ListItem value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

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

例: Keyの正しい使い方

function ListItem(props) {
  // Correct! There is no need to specify the key here:
  return <li>{props.value}</li>;
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // Correct! Key should be specified inside the array.
    <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')
);

Try it on CodePen.

いけているルールは、map()の中で要素にkeyをセットすることです。

Keyはただユニークであるべき

配列の中で使われるkeyはその中でユニークにすべきです。
しかし、グローバルなユニークである必要はありません。
同じkeyを異なる二つの配列で使ってもOKです。


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')
);

Try it on CodePen.

CodePenで試してみてください

keyはヒントとしてReactに渡されますが、
コンポーネントを飛び越えることはありません。
コンポーネントの中で同じ値が必要な場合、
propに別名で入れて明確に渡してください。

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

上記の例では、Postコンポーネントはprops.idを参照できますが、
props.keyとは異なります。

JSX内へmap()を組み込む

下記の例では、分離したlistItem変数を定義し、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()の結果もinlineできます。

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

Try it on CodePen.

時に、クリアなコードになりますが、しかしこのスタイルは乱用されることもあります。
Javascriptのように可読性のために変数の抽出に価値を持たせるかどうかはあなた次第です。
map()関数のはかなりネストすることを肝に銘じておいてください、
それはコンポーネントを抽出するにはいい機会だと思います。

⚠️ **GitHub.com Fallback** ⚠️