mapメソッドに不可欠なunique "key" prop の役割
配列に格納した要素をmapメソッドで一覧表示した時の事です。
コードは正常に動いている一方で警告メッセージが出現。
別になくても良くない?と思ったので、なぜ unique "key" prop が大切なのか調べてみました。
リストコンポーネントと Key
公式ドキュメントにはkeyの必要性について以下のような説明がありました。
Key は、どの要素が変更、追加もしくは削除されたのかを React が識別するのに役立ちます。配列内の項目に安定した識別性を与えるため、それぞれの項目に key を与えるべきです。
もちろん一意キーがあった方が識別性は高まると思います。ただ、リストの子要素それぞれにそれを設定しなくても一覧表示する分には問題がないとも考えてしまいます。
Unique Key が必要な理由
一意キーがサポートされている理由はReactの仕組み(差分アルゴリズム)にありました。
デフォルトでは、DOM ノードの子に対して再帰的に処理を行う場合、React は単純に、両方の子要素リストのそれぞれ最初から同時に処理を行っていって、差分を見つけたところで毎回更新を発生させます。
どうやらReactでは、それぞれの木構造を同時に読み込み一つずつ上から下に処理するようです。
この仕組みから以下のような場合は問題がないようです。
<ul>
<li>one</li>
<li>two</li>
</ul>
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
one
,two
までは一緒なのでスルー、最後のthree
に辿り着いた時に更新されます。
問題点は非効率
問題は以下のように子要素が先頭に追加されているような場合です。
<ul>
<li>one</li>
<li>two</li>
</ul>
<ul>
<li>zero</li>
<li>one</li>
<li>two</li>
</ul>
Reactはこの差分を検知するシンプルに上から下に比べていきます。
この場合はone
はzero
,two
はone
に更新され、最後にtwo
が挿入されることになります。
これは確かに効率が悪い!
ReactはKey属性をサポートすることでこの問題を解決しているようです。
<ul>
<li key="1">one</li>
<li key="2">two</li>
</ul>
<ul>
<li key="0">zero</li>
<li key="1">one</li>
<li key="2">two</li>
</ul>
一意キーの値によって新規の子要素かどうかの判別が着くので、新規でない子要素は保守されたまま更新することができます。
アプリの動作に支障は出るのか?
どんな問題が起こりうるのかを再現するためにあえて Key なしでアプリを作ってみました。
試しに午前9時の"おはよう"を"こんばんわ"に変更して削除してみます。
すると削除したはずの午前9時の"こんばんわ"が午後1時に引き継がれてしまいます。
一方で、最後の午後9時の"おはよう"を"こんばんわ"に変更して削除しても問題は発生しません。
先ほどの比較のように、先頭の子要素の変化があった場合は他の子要素との識別ができないためにこのようなエラーが発生してしまうと思われます。
Summary
最初にこの警告メッセージをみた時はあまりピンときませんでしたが今はものすごく納得しました。
配列内の子要素には一意キーを持たせること!これだけ覚えて寝ます。
Reference
Discussion