🎯 この記事のゴール
- Vue3でTODOアプリをゼロから作れる
- 作るだけでなく「なぜそうするか」を理解する
- 実務でも通用する書き方を身につける
🧠 完成イメージ
- TODO追加
- 完了チェック
- 削除
- 一覧表示
👉 シンプルだけどVueの基礎が全部詰まっています
🚀 Step0:環境構築
npm create vite@latest todo-app
cd todo-app
npm install
npm run dev
🧩 Step1:状態(データ)を定義
<script setup>
import { ref } from 'vue'
const todos = ref([])
const newTodo = ref('')
</script>
👉 ポイント
-
todos→ リスト -
newTodo→ 入力値
✍️ Step2:TODO追加機能
<script setup>
const addTodo = () => {
if (!newTodo.value) return
todos.value.push({
id: Date.now(),
text: newTodo.value,
done: false
})
newTodo.value = ''
}
</script>
🧠 注意点①:空入力を防ぐ
👉 実務では必須
🖥 Step3:テンプレート作成
<template>
<input v-model="newTodo" placeholder="TODOを入力" />
<button @click="addTodo">追加</button>
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }}
</li>
</ul>
</template>
🧠 注意点②:必ずkeyをつける
👉 DOM更新のバグ防止
✅ Step4:完了機能
const toggleTodo = (id) => {
const todo = todos.value.find(t => t.id === id)
if (todo) todo.done = !todo.done
}
<li v-for="todo in todos" :key="todo.id">
<input type="checkbox" @change="toggleTodo(todo.id)" />
<span :style="{ textDecoration: todo.done ? 'line-through' : 'none' }">
{{ todo.text }}
</span>
</li>
🧠 注意点③:状態を直接変更する
👉 VueはリアクティブなのでOK
🗑 Step5:削除機能
const deleteTodo = (id) => {
todos.value = todos.value.filter(t => t.id !== id)
}
<button @click="deleteTodo(todo.id)">削除</button>
🧠 注意点④:配列操作は再代入
👉 filter後は再代入が基本
🎯 完成コード
<script setup>
import { ref } from 'vue'
const todos = ref([])
const newTodo = ref('')
const addTodo = () => {
if (!newTodo.value) return
todos.value.push({
id: Date.now(),
text: newTodo.value,
done: false
})
newTodo.value = ''
}
const toggleTodo = (id) => {
const todo = todos.value.find(t => t.id === id)
if (todo) todo.done = !todo.done
}
const deleteTodo = (id) => {
todos.value = todos.value.filter(t => t.id !== id)
}
</script>
<template>
<input v-model="newTodo" placeholder="TODOを入力" />
<button @click="addTodo">追加</button>
<ul>
<li v-for="todo in todos" :key="todo.id">
<input type="checkbox" @change="toggleTodo(todo.id)" />
<span :style="{ textDecoration: todo.done ? 'line-through' : 'none' }">
{{ todo.text }}
</span>
<button @click="deleteTodo(todo.id)">削除</button>
</li>
</ul>
</template>
🧠 実務で差がつく注意点
① IDの設計
❌ Date.now()だけ
👉 衝突の可能性あり
✅ UUIDなどを検討
② 状態の責務
👉 今回は1ファイルだが実務では分割
- ロジック
- UI
③ バリデーション
- 空入力
- 長さ制限
👉 UIだけでなくロジックでもチェック
④ パフォーマンス
- リストが増えた場合
- 再描画の影響
👉 keyが重要
⑤ 永続化(次のステップ)
👉 リロードで消える問題
対策:
- localStorage
- API保存
🚀 レベルアップ課題
ここまでできたら👇
- フィルタ(未完了 / 完了)
- 並び替え
- 編集機能
- 保存機能
🎯 よくある失敗
- refを忘れる
- keyをつけない
- 状態とUIが混ざる
- 直接DOM操作する
🔥 一番重要なポイント
👉 「状態(データ)を中心に考える」
🔚 まとめ
Vue3でTODOを作ると👇が身につく
- 状態管理(ref)
- イベント処理
- リスト操作
- UI更新