無気力エンジニア

大好きな技術の話や趣味の話を書きます

ReactコンポーネントのKey(キー)の付け方

はじめに

最近Reactを勉強し始めて、map()などのループでコンポーネントを作成するときに、Keyはどのように付けるべきなのか疑問に思い、調査したことをまとめて紹介します。

参考文献

この記事は以下の情報を参考に執筆しました。

インデックスをReactコンポーネントのKeyとして使用しない

配列のインデックスを、ReactコンポーネントのKeyとして使用しないでください。map()イテレータやループを使用したときに、Keyとしてインデックスを利用するパターンはよく見られます。または、Math.random()などを利用して乱数を使用するのがおなじみのパターンではないでしょうか。

Keyの役割

  1. ReactはKeyを利用して、何をレンダリングまたは再レンダリングする必要があるかを決定します。つまり、Keyはコンポーネントの識別に利用されます。
  2. Reactは、レンダリングの時間を節約するため、重複したレンダリングなどをしないように動作します。そのため、2つの要素があり、それらが同じKeyを持っている場合、Reactはそれらを同じ物とみなし、一部の要素のレンダリングが省略される可能性があります。
  3. Reactは、コンテンツ自体が変更されていない場合でも、Keyが変更された要素を再レンダリングします。
  4. これが、Keyとしてインデックスを使用することが、良くない考えである主な理由です。

2について、例を出して説明します。例えば、現在読んでいる本の一覧を表示させるとします。以下のように、本の名前と著者の2つのキーバリューのペアを持つオブジェクトの配列を作成します。これらの本をレンダリングするもっとも簡単な方法は、map()を使用してそれを反復処理し、li要素を返すことです。またインデックスをKeyとして使ってみましょう。

import React from 'react'

// 本のリスト
let bookListData = [
  {
    title: 'The Hard Things About Hard Things',
    author: 'Ben Horowitz'
  }, {
    title: 'Only the Paranoid Survive',
    author: 'Andrew S. Grove'
  }, {
    title: 'Lean Startup',
    author: 'Eric Ries'
  }, {
    title: 'Fullstack React',
    author: 'Anthony Accomazzo'
  }
]

// 本のリストを返す
const BookList = () => {
  return(
    <ul>
      {
        bookListData.map((book, index) => <li key={index}>{book.title} by {book.author}</li>)
      }
    </ul>
  )
}

インデックスをKeyとして使用する際の問題

では、本の配列を変更するとどうなるでしょうか。例えば、新しい本を追加することにしましょう。確かに、Reactはリストを再レンダリングし、DOM構造を更新します。問題は、Reactがどれくらいの数の要素を更新するかということです。

// 配列の先頭に新しい本を追加します。
bookListData.unshift({
  title: 'Elon Musk',
  author: 'Ashlee Vance'
})

答えは、すべての要素です。追加した本が1冊だけで、前の4冊が同じであるかどうかは関係ありません。Reactが認識しているのは、特定のKeyを持つ4つの要素があり、それらのKeyがすべて変更されてることだけです。配列の先頭に、本を追加することで、すべての本のインデックスも変更されます。

レンダリングについて上記で説明したように、Reactは、コンテンツ自体が変更されていない場合でも、Keyが変更された要素を再レンダリングします。新しい本を追加することで、他の本のインデックスも変更され、すべての要素の再レンダリングが起こりました。これらは必要だったのでしょうか?

答えはいいえです。Reactはすべての本を再レンダリングする必要はありませんでした。Reactは、元々あったDOM構造を使用して、1つの新しい要素、つまり1つの新しい本を追加するだけで済んだはずです。

また、Math.random()を利用すると同様の問題が発生します。新しい本を追加すると、再レンダリングが発生します。新しいレンダリングでは、random()メソッドもトリガーされ、新しいKeyが生成されます。それにより、すべての要素が再レンダリングされてしまうのです。

Keyのシンプルなルール

では、これらの問題を回避するために、どのようにルールでKeyを付与すればよいでしょうか。

  1. 要素のKeyは常に一意である必要があります。グローバルスコープやプロジェクトスコープで一意である必要はありません。兄弟の間だけでユニークである必要があります。
  2. 同じ要素のKeyは、ページの更新や要素の並べ替えなど、時間の経過とともに変化しないようにする必要があります。つまり、キーは不変で、定数である必要があります。
    • これはインデックスの例で説明した、問題を回避するためのルールです。
  3. Keyは予測可能である必要があります。Keyがランダムに生成されないようにする必要があります。

これらの、シンプルで簡単なルールを覚えて、Keyを追加してみましょう。

import React from 'react'

// 本のリスト
let bookListData = [
  {
    title: 'The Hard Things About Hard Things',
    author: 'Ben Horowitz',
    isbn: '978-0547265452'
  }, {
    title: 'Only the Paranoid Survive',
    author: 'Andrew S. Grove',
    isbn: '978-0385483827'
  }, {
    title: 'Lean Startup',
    author: 'Eric Ries',
    isbn: '978-0307887894'
  }, {
    title: 'Fullstack React',
    author: 'Anthony Accomazzo',
    isbn: '978-0991344628'
  }
]

// 配列の先頭に新しい本を追加する
bookListData.unshift({
  title: 'Elon Musk',
  author: 'Ashlee Vance',
  isbn: '978-0062301239'
})

// 本のリストを返す
const BookList = () => {
  return(
    <ul>
      {
        bookListData.map(book => <li key={book.isbn}>{book.title} by {book.author}</li>)
      }
    </ul>
  )
}

上記のように、isbnのような識別子を、Keyとして利用することですべての要素が再レンダリングされる問題を回避することができました。