【React】DOMノードにkeyを設定して再描画を最適化する

# 概要
[公式ドキュメント](https://ja.reactjs.org/docs/lists-and-keys.html)に記載の通り、ループ処理内で(V)DOMノードを描画する場合はkeyの設定が推奨され、設定が無ければWarningがコンソールに出力される。keyを設定する意義について再度ドキュメントを読み直して調べてみると、再描画時の[差分検出](https://ja.reactjs.org/docs/reconciliation.html)を最適化するのに役立つと記載されている。この挙動を検証した結果を以下に書き記す。

# ソースコード
<p class="codepen" data-height="265" data-theme-id="0" data-default-tab="js,result" data-user="takuyahara" data-slug-hash="Zgzgpv" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Usage of Key for ideal reconciliation">
  <span>See the Pen <a href="https://codepen.io/takuyahara/pen/Zgzgpv/">
  Usage of Key for ideal reconciliation</a> by Takuya HARA (<a href="https://codepen.io/takuyahara">@takuyahara</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

# 調査方法
描画される内容を全画面で確認するため、[Debugモードのページ](https://s.codepen.io/takuyahara/debug/Zgzgpv/NQMzYqVZJWwk)へ移動してDeveloper Toolsを開く。Elementsタブを選択して操作対象の\<ul\>タグを展開し、\<li\>タグが表示された状態で各種ボタンをクリックすると、\<li\>タグの再描画が確認できる。keyありとkeyなしの場合で、それぞれどのように再描画が行われるかを確認する。

# 調査結果
## keyあり
### リストに新しい値を末尾に追加する
<img width="1450" alt="Screen Shot 2019-07-19 at 0.31.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286440/34bb44ed-a979-7cbf-46c3-a299c22b3e90.png">
末尾の\<li\>の開始タグが点滅している。これは当該の\<li\>タグが新たに追加されたことを意味する。
追加分のタグだけが再描画されているため、理想的な挙動である。

### リストに新しい値を先頭に追加する
<img width="1450" alt="Screen Shot 2019-07-19 at 0.31.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286440/9c4f1999-cb0b-0f8d-2116-cfa1d1365e86.png">
先頭の\<li\>の開始タグが点滅している。
理由は同上であり、理想的な挙動である。

### リストに新しい値を真ん中に追加する
<img width="1450" alt="Screen Shot 2019-07-19 at 0.31.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286440/766ae0b8-e511-1244-425b-20e8313cf758.png">
真ん中の\<li\>の開始タグが点滅している。
理由は同上であり、理想的な挙動である。

## keyなし
### リストに新しい値を末尾に追加する
<img width="1450" alt="Screen Shot 2019-07-19 at 0.32.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286440/376da430-1c69-0abf-b671-87118c8208a3.png">
末尾の\<li\>の開始タグが点滅している。
必要分のDOMノードの更新のみ行われているため、理想的な挙動である。

これは描画済みのDOMツリーと再描画用のDOMツリーが以下のように比較されたからである。上から比較して\<li\>0\</li\>から\<li\>4\</li\>まで一致し、\<li\>5\</li\>のみ一致しない（描画済みDOMツリーに対応するノードが無い）ため、新規に挿入している。\<li\>5\</li\>はタグ自体が新たに描画されたため、開始タグが点滅している。

```
  描画済み           再描画用
 DOMツリー          DOMツリー
<li>0</li>        <li>0</li>
<li>1</li>        <li>1</li>
<li>2</li>        <li>2</li>
<li>3</li>        <li>3</li>
<li>4</li>        <li>4</li>
                  <li>5</li> (新規挿入)
```

### リストに新しい値を先頭に追加する
<img width="1450" alt="Screen Shot 2019-07-19 at 0.32.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286440/e923a7b5-5814-c96a-758c-1ed2e53fde4c.png">
全ての\<li\>の開始タグ、および末尾以外の\<li\>タグ内の値が点滅している。開始タグとタグ内の値が点滅しているのは、タグ内の値が更新されたことを示している。
無用なDOMノードの更新が行われているため、非効率的な処理をしている。

これは描画済みのDOMツリーと再描画用のDOMツリーが以下のように比較されたからである。上から比較して6つの\<li\>はタグ名が一致するがタグ内の値が異なるため値のみを更新し、開始タグとタグ内の値が点滅して示されている。\<li\>5\</li\>はタグ自体が新たに描画されたため、開始タグのみが点滅している。

```
  描画済み           再描画用
 DOMツリー          DOMツリー
<li>0</li>        <li>6</li> (値を更新)
<li>1</li>        <li>0</li> (値を更新)
<li>2</li>        <li>1</li> (値を更新)
<li>3</li>        <li>2</li> (値を更新)
<li>4</li>        <li>3</li> (値を更新)
<li>5</li>        <li>4</li> (値を更新)
                  <li>5</li> (新規挿入)
```

### リストに新しい値を真ん中に追加する
<img width="1450" alt="Screen Shot 2019-07-19 at 0.32.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/286440/31cf1326-7147-c9bb-0215-d9acdd1c00b0.png">
挿入した箇所以降における\<li\>の開始タグ、および末尾以外の\<li\>タグ内の値が点滅している。
無用なDOMノードの更新が行われているため、非効率的な処理をしている。

これは描画済みのDOMツリーと再描画用のDOMツリーが以下のように比較されたからである。上から比較して、新しい値が挿入された4番目以降の\<li\>はタグ名が一致するがタグ内の値が異なるため値のみを更新し、開始タグとタグ内の値が点滅して示されている。\<li\>5\</li\>はタグ自体が新たに描画されたため、開始タグのみが点滅している。

```
  描画済み           再描画用
 DOMツリー          DOMツリー
<li>6</li>        <li>6</li>
<li>0</li>        <li>0</li>
<li>1</li>        <li>1</li>
<li>2</li>        <li>7</li> (値を更新)
<li>3</li>        <li>2</li> (値を更新)
<li>4</li>        <li>3</li> (値を更新)
<li>5</li>        <li>4</li> (値を更新)
                  <li>5</li> (新規挿入)
```

# 考察
Reactの[差分アルゴリズム](https://ja.reactjs.org/docs/reconciliation.html#the-diffing-algorithm)により、keyを指定しないと無用なDOMノードの再描画が行われることが分かった。フロントエンド側のチューニング手法の一つとして、ループ処理内で(V)DOMを描画する場合はkeyを忘れないようにしよう。