MENU

Vue3 TODOアプリ実践|v-forでkeyにindexを使ってはいけない理由

Vue3 TODOアプリ実践|v-forでkeyにindexを使ってはいけない理由

〜「動く」から「壊れにくい」設計へ〜

VueでTODOアプリを作れるようになってきた頃、
多くの人が こんな書き方をしています。

<li v-for="(todo, index) in todos" :key="index">

学習としては正解です。
でも 実務ではこの書き方は避けます。

この記事では、

  • なぜ indexkey にしてはいけないのか
  • どう直すのが正解なのか
  • Vue3での「一段上の書き方」

順を追って説明します。

Vue3のkeyについての解説画像

目次

なぜ key=”index” は危険なのか?

Vueは画面を更新するとき、
こう考えています。

「前と同じ要素か?それとも別物か?」

その判断材料が key です。


問題が起きる具体例

todos = [
  "勉強する",
  "買い物に行く",
  "洗濯する"
];

表示されている状態:

0: 勉強する
1: 買い物に行く
2: 洗濯する

ここで 真ん中を削除すると…

[
  "勉強する",
  "洗濯する"
]

indexはこう変わります。

内容前のindex削除後
勉強する00
洗濯する21

Vueはこう誤解することがあります👇

「あれ? index=1 は前もあったな…
中身が変わっただけか」

👉 別のTODOなのに、同じものとして扱われる

これが バグの温床になります。


解決策:TODOに「id」を持たせる

現場では 配列の順番ではなく、データ自体を識別します。

TODOの形を変える

{
  id: 123456789,
  text: "勉強する",
  done: false
}

Vue3(Composition API)での実装

script(ロジック部分)

<script setup>
import { ref, computed } from "vue";

const text = ref("");
const todos = ref([]);

TODOを追加する

const addTodo = () => {
  if (!text.value.trim()) return;

  todos.value.push({
    id: Date.now(),   // 一意なID
    text: text.value,
    done: false
  });

  text.value = "";
};

なぜ Date.now()

  • 毎回ほぼ被らない数値
  • 学習用途には十分
  • 実務では UUID を使うこともある

削除処理(indexを使わない)

const removeTodo = (id) => {
  todos.value = todos.value.filter(todo => todo.id !== id);
};

ポイント

  • filter新しい配列を作る
  • Vueは 変更を確実に検知できる
  • index に依存しない

完了状態を切り替える

const toggleTodo = (id) => {
  const todo = todos.value.find(todo => todo.id === id);
  if (todo) {
    todo.done = !todo.done;
  }
};

残り件数を表示する(computed)

const remainingCount = computed(() => {
  return todos.value.filter(todo => !todo.done).length;
});

なぜ computed?

  • todos が変わったときだけ再計算
  • 自動で最新の値になる
  • 表示用ロジックに最適

template(完成版)

<template>
  <div>
    <input v-model="text" />
    <button @click="addTodo">追加</button>

    <p>残り {{ remainingCount }} 件</p>

    <ul>
      <li
        v-for="todo in todos"
        :key="todo.id"
      >
        <span
          @click="toggleTodo(todo.id)"
          :style="{ textDecoration: todo.done ? 'line-through' : 'none' }"
        >
          {{ todo.text }}
        </span>

        <button @click="removeTodo(todo.id)">削除</button>
      </li>
    </ul>
  </div>
</template>

この実装で身につく考え方

初心者今回
indexで操作idで操作
表示順に依存データ基準
動けばOK壊れにくい

まとめ

  • key見た目用ではなく識別子
  • index は変わるが、id は変わらない
  • Vueでは 「データ設計」が重要

この考え方ができるようになると、

  • コンポーネント分割
  • localStorage 保存
  • 実務案件

すべて一気に理解しやすくなります。

その他関連記事はこちら→プログラミング備忘録

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

初心者の私がコーディングをしていて躓いたところをピックアップして日々の備忘録として発信しています^^
楽しくコーディングを身に着けていけるように日々勉強中です★

コメント

コメントする

目次