【React】配列のkeyにインデックスを使うのは辞めようねというお話

## 初めに

React でリストをレンダリングするときに、`map` を使って `key` を指定することは必須です。

`key` の指定方法として **インデックスを使う方法** をよくUdemy講座や初心者向けの解説記事などで散見します。

私も最初はindexを利用したコーディングをしていたのですが、
勉強していく中でそれは思わぬバグを踏む可能性や効率的なレンダリングではないことに気づいたので記事にします。

実際に React の公式ドキュメントでも `key` には一意の識別子を使うことを推奨していますが、なぜインデックスを `key` に使うのが問題になるのでしょうか？

▼以下参考

https://ja.react.dev/learn/rendering-lists#rules-of-keys

この記事では、以下の流れで `key` に関する問題を詳しく解説し、適切な方法を紹介していきます。

### 記事の流れ
1.**`key`を設定しないと何が問題なのか？**
2. **`key` にインデックスを使うと何が問題なのか？**
3. **ではどうすればよいのか？**
4. **まとめ**

各コードの実行結果のスクリーンショットや、コンソールでのレンダリング状況も掲載するので、実際にどのような挙動になるのかも分かりやすく解説します。
また、そもそもkeyなんて知らないよという方も読んで雰囲気がわかるようにはなっているので、Reactを触り始めた方にはぜひ読んでほしいです。

それでは、まず `key` にインデックスを使うと何が問題になるのかを見ていきましょう！

## `key` を設定しないと何が問題なのか？

React では、リストをレンダリングするときに `key` を指定しないと、要素の差分を適切に管理できず、不要な再レンダリングが発生することがあります。

以下のコードを見てみましょう。

```jsx
import { useState } from 'react';
import './App.css';

function App() {
  const [tasks, setTasks] = useState(["タスク1", "タスク2", "タスク3"]);
  const [newTask, setNewTask] = useState("");

  const addTask = () => {
    if (newTask.trim() === "") return;
    setTasks([...tasks, newTask]);
    setNewTask("");
  };

  return (
    <>
      <h1>タスク管理アプリ</h1>
      <input
        type="text"
        value={newTask}
        onChange={(e) => setNewTask(e.target.value)}
        placeholder="新しいタスクを入力"
      />
      <button onClick={addTask}>追加</button>
      <ul>
        {tasks.map((task) => (
          <li>{task}</li>
        ))}
      </ul>
    </>
  );
}

export default App;
```

このコードでは `key` を指定していません。そのため、リストの更新時に React はどの要素が新しく追加されたのかを正しく識別できず、不要な再レンダリングが発生する可能性があります。

### 実行画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3909033/8b1874fa-b696-f2a2-1103-4b1255fe8130.png)

### コンソールの様子
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3909033/9eb73720-6efb-34a4-fa42-96c9b30607c7.png)

keyにはしっかりとユニークなIDを追加しようね
と怒られていますね

次に、`key` にインデックスを使った場合にどのような問題が起こるのかを見ていきましょう。

## `key` にインデックスを使うと何が問題なのか？

`いったんの解決策`や`初心者向け説明`でkeyにindexを設定しているコードを散見します。
`key` にインデックスを指定することは一見簡単な解決策に思えます。
しかし、実際には以下のような問題が発生します。

### コード例

```jsx
import { useState } from 'react';
import './App.css';

function App() {
  const [tasks, setTasks] = useState(["タスク1", "タスク2", "タスク3"]);
  const [newTask, setNewTask] = useState("");

  const addTask = () => {
    if (newTask.trim() === "") return;
    setTasks([...tasks, newTask]);
    setNewTask("");
  };

  return (
    <>
      <h1>タスク管理アプリ</h1>
      <input
        type="text"
        value={newTask}
        onChange={(e) => setNewTask(e.target.value)}
        placeholder="新しいタスクを入力"
      />
      <button onClick={addTask}>追加</button>
      <ul>
        {tasks.map((task, index) => (
          <li key={index}>{task}</li>
        ))}
      </ul>
    </>
  );
}

export default App;
```

### `key` にインデックスを使うと発生する問題

1. **リストの順番が変わると、React が正しく識別できない**
   - 並び替えが発生すると、React が要素を誤認識し、意図しないレンダリングが起こることがあります。

2. **新しい要素が追加・削除されると、不要な再レンダリングが発生する**
   - 先頭に新しい要素を追加すると、すべてのインデックスが変わるため、React が正しく認識できずに過剰な更新が発生します。

### 実行画面とコンソールの様子
![Videotogif (2).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3909033/972a56be-3758-f942-2fe5-c8edfbd3b2b5.gif)

## コンソールの挙動の原因

#### **追加前:**
```
0 → "タスク1"
1 → "タスク2"
2 → "タスク3"
```

初期で上のようなリストが、追加をすることで以下のようになります。

#### **追加後:**
```
0 → "新しいタスク" (本来のタスク1の `key=0` に変更)
1 → "タスク1" (本来のタスク2の `key=1` に変更)
2 → "タスク2" (本来のタスク3の `key=2` に変更)
3 → "タスク3"
```

このように、既存のすべての `key` がズレてしまい、React が「リストのすべての要素が変わった」と誤認識してしまいます。
reactの強みである仮想DOMによる差分だけのレンダリングが上手に機能していませんね。

## ではどうすればよいのか？

`key` にインデックスを使うと問題が発生するため、**リストの各要素にユニークな識別子（ID）を `key` に設定する** ことが推奨されます。

### **適切な `key` の設定方法**

```jsx
import { useState } from 'react';

function App() {
  const [tasks, setTasks] = useState([
    { id: 1, name: "掃除" },
    { id: 2, name: "洗濯" },
    { id: 3, name: "料理" }
  ]);
  const [newTask, setNewTask] = useState("");

  const addTask = () => {
    if (newTask.trim() === "") return;
    const newTaskObj = { id: Date.now(), name: newTask };
    setTasks([newTaskObj, ...tasks]);
    setNewTask("");
  };

  return (
    <>
      <h1>タスク管理アプリ</h1>
      <input
        type="text"
        value={newTask}
        onChange={(e) => setNewTask(e.target.value)}
        placeholder="新しいタスクを入力"
      />
      <button onClick={addTask}>追加</button>
      <ul>
        {tasks.map((task) => (
          <li key={task.id}>{task.name}</li>
        ))}
      </ul>
    </>
  );
}

export default App;
```

### 実行画面とコンソールの様子
▼しっかりと追加した分だけ追加でレンダリングされていますね
![Videotogif (5).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3909033/0041b78e-220d-80e7-4f65-780e505ca4a0.gif)



### **なぜこのような書き方が良いか？**
1. **要素が並び替えられても `key` が変わらないため、React が適切に差分を認識できる**
2. **要素を追加・削除しても、既存の要素の `key` が変わらず、不要な再レンダリングを防げる**

このように、適切な `key` を設定することで、React のレンダリングが最適化され、意図しない挙動を防ぐことができます。

まとめ

key を設定しないと、React が要素の変更を適切に判断できず、不要な再レンダリングが発生する。

key にインデックスを使用すると、リストの並び替えや要素の追加・削除時に key がずれてしまい、意図しないレンダリングが起こる。

最適な key 設定は、ユニークな識別子（ID）を使用すること。

key を適切に設定することで、React の仮想DOMが最大限活かされ、効率的なレンダリングが実現できる。

React でリストを扱う際には、常に key の適切な指定を意識し、最適なパフォーマンスを確保しましょう！

## まとめ

- `key` を設定しないと、React が要素の変更を適切に判断できず、不要な再レンダリングが発生する。
- `key` にインデックスを使用すると、リストの並び替えや要素の追加・削除時に `key` がずれてしまい、意図しないレンダリングが起こる。
- **最適な `key` 設定は、ユニークな識別子（ID）を使用すること**。
- `key` を適切に設定することで、React の仮想DOMが最大限活かされ、効率的なレンダリングが実現できる。

React でリストを扱う際には、常に `key` の適切な指定を意識し、最適なパフォーマンスを確保しましょう！