採点されません。匿名です。 間違えることは学習の一部です。読むだけでもかまいません。

L08 ch05 — リスト + フォーム (TODO アプリ)

テクノロジ系 / L08 vue-basic — Vue 3 入門

L08 ch05 — リスト + フォーム (TODO アプリ)

v-model で双方向バインド + リスト操作

ゴール

  • v-model で input ↔ 変数を双方向バインド
  • TODO アプリの 4 機能 (追加/削除/完了切替/集計)
  • computed で派生値

v-model

<script setup>
import { ref } from 'vue'
const title = ref('')
</script>

<template>
  <input v-model="title" placeholder="タスク名" />
  <p>入力中: {{ title }}</p>
</template>

→ input の値と変数が 常に同期

追加機能

<script setup>
const tasks = ref([])
const newTitle = ref('')

function addTask() {
  if (!newTitle.value.trim()) return
  tasks.value.push({
    id: Date.now(),
    title: newTitle.value,
    done: false,
  })
  newTitle.value = ''
}
</script>

<template>
  <input v-model="newTitle" />
  <button @click="addTask">追加</button>
</template>

削除機能

<script setup>
function removeTask(id) {
  tasks.value = tasks.value.filter(t => t.id !== id)
}
</script>

<template>
  <li v-for="t in tasks" :key="t.id">
    {{ t.title }}
    <button @click="removeTask(t.id)">削除</button>
  </li>
</template>

完了切替 (チェックボックス)

<li v-for="t in tasks" :key="t.id">
  <input type="checkbox" v-model="t.done" />
  <span :style="{ textDecoration: t.done ? 'line-through' : 'none' }">
    {{ t.title }}
  </span>
</li>

computed で派生値

<script setup>
import { computed } from 'vue'

const doneCount = computed(() => tasks.value.filter(t => t.done).length)
const undoneCount = computed(() => tasks.value.length - doneCount.value)
</script>

<template>
  <p>未完了: {{ undoneCount }} / 全 {{ tasks.length }}</p>
</template>

→ tasks が変わると自動再計算

リストから項目を削除する

「id で消す (filter)」と「位置で消す (splice)」の 2 パターンがある。

<script setup>
const tasks = ref([
  { id: 1, title: '買い物' },
  { id: 2, title: '掃除' },
])

// パターン A: index 指定で 1 件抜く (破壊的)
function removeByIndex(index) {
  tasks.value.splice(index, 1)
}

// パターン B: 条件で絞り込んで新配列に置き換え (非破壊)
function removeById(id) {
  tasks.value = tasks.value.filter(t => t.id !== id)
}
</script>

<template>
  <li v-for="(t, i) in tasks" :key="t.id">
    {{ t.title }}
    <button @click="removeByIndex(i)">削除</button>
  </li>
</template>
  • splice(index, 1)その位置の要素 1 件を抜くv-for の第 2 引数 i を渡せば素直
  • filter条件に合うものだけ残す。id を持っているなら可読性が高い
  • どちらでも動くが、Vue では tasks.value 自体を更新するか、splice で中身を直接いじるかの違い

drill

drill/ — fill-task-app, add-task-app

ハンズオン

ch05-list-form 演習ガイド

受講生向け演習問題集

採点の仕組み (読んでから始めてください)

採点テストは 「お題の通りに動くか」 を機械的にチェックします。

ドリルごとに 3つのタイプ があるので、各ドリル冒頭の 【タイプ】 を確認してください。

タイプ fill (穴埋め問題) — 文法・APIを知っているか

  • ___ (3個並んだアンダースコア) の空欄を埋める
  • 既存の <template> のテキストやボタン順は そのまま
  • ロジック自由度: 低 (お題どおりに直す)

タイプ add (機能追加問題) — 既存コードに機能を足せるか

  • 既存コードを残したまま、新しい機能を 追加 する
  • 実装方法は自由 (関数名・変数名・内部ロジックは好きに)
  • ただしテストが要求する見た目 (表示テキスト・ボタンの並び順) は守る
  • 既存テストを壊さないこと

タイプ build (ゼロベース実装問題) — 仕様から実装を組み立てられるか

  • ほぼ空の App.vue から 自力で全部書く
  • <template> 構造・id/class・内部ロジック: 完全に自由
  • 守るのは画面の機能仕様だけ: 表示するテキスト・ボタンの操作で何が起きるか
  • 一番ハードだが、実装力が一番つくタイプ

3タイプ共通で守ること

  • ファイル名 (App.vue) は変えない
  • tests/ フォルダは触らない (採点ロジック)
  • テンプレートの 表示テキスト (「カウント:」等) はテストが見ているので残す

このガイドのドリル一覧

#ドリル名タイトルタイプ難易度
101-fill-task-appv-model + push - フォーム入力でリスト追加🔧 穴埋め
202-add-task-appv-model + バリデーション - タスク追加機能の拡張➕ 機能追加
303-build-task-appv-model + push + splice + v-for - タスク管理アプリを白紙から🛠️ ゼロ実装★★

> 詰まったら ヒント ページを開く。それでも分からなければ講師に質問。

ドリル 1: v-model + push - フォーム入力でリスト追加

ドリル名: 01-fill-task-app

> 🔧 【タイプ: 穴埋め問題】 ___ の部分を埋めて完成させてください。既存のコード構造は変えないこと。

問題

___ を埋めて、入力したタスクを追加・削除できるアプリを完成させよ。___ は全部で5箇所ある。

<script setup>
import { ref } from 'vue'
const newTitle = ref('')
const tasks = ref([])

function addTask() {
  if (!newTitle.value.trim()) return
  tasks.value.___({ id: Date.now(), title: newTitle.value })   <!-- ___1: 配列末尾に追加するメソッド -->
  newTitle.value = ''
}

function removeTask(id) {
  tasks.value = tasks.value.___(t => t.id !== id)              <!-- ___2: 配列から条件で除外するメソッド -->
}
</script>

<template>
  <input v-___="newTitle" placeholder="タスク名" />              <!-- ___3: 双方向バインディング -->
  <button @click="addTask">追加</button>

  <ul>
    <li v-___="task in tasks" :___="task.id">                  <!-- ___4: 配列ループ / ___5: 識別キー -->
      {{ task.title }}
      <button @click="removeTask(task.id)">削除</button>
    </li>
  </ul>
</template>

確認方法

  1. App.vue に貼り付けて npm run dev で起動
  2. テキストを入力→「追加」でリストに表示される
  3. 「削除」ボタンで該当タスクが消える
  4. F12 コンソールに警告が出ないこと

ドリル 1: ヒント

ドリル名: 01-fill-task-app

> ⚠️ 自力で考えてから読んでください

  • ___1: 配列末尾に要素を追加する JavaScript の配列メソッド
  • ___2: 配列から条件に合う要素だけを残す JavaScript の配列メソッド
  • ___3 / ___4: Vue のディレクティブは v-モデル / v-繰り返し
  • ___5: v-for の各要素を識別するための属性(: から始まる)

ドリル 2: v-model + バリデーション - タスク追加機能の拡張

ドリル名: 02-add-task-app

> ➕ 【タイプ: 機能追加問題】 既存コードを残したまま新しい機能を追加してください。実装は自由ですが、テストが要求する I/F・表示テキストは守ること。

ベースコード(base/App.vue)は「追加」だけ動く状態。以下の機能を追加せよ。

追加する機能

  1. 完了切り替え: チェックボックスをクリックすると done が反転して、タイトルに打ち消し線がつく
  2. 削除: 「削除」ボタンをクリックすると該当タスクがリストから消える
  3. 件数表示: 「未完了 X 件 / 全 Y 件」を画面下部に表示する

ベースコード(base/App.vue)

<script setup>
import { ref } from 'vue'
const newTitle = ref('')
const tasks = ref([])

function addTask() {
  if (!newTitle.value.trim()) return
  tasks.value.push({ id: Date.now(), title: newTitle.value, done: false })
  newTitle.value = ''
}
// ここに toggleDone / deleteTask を追加する
</script>

<template>
  <h1>タスク一覧</h1>
  <input v-model="newTitle" placeholder="タスク名を入力" />
  <button @click="addTask">追加</button>
  <ul>
    <li v-for="task in tasks" :key="task.id">
      <!-- ここに完了切り替え・削除を追加する -->
      {{ task.title }}
    </li>
  </ul>
  <!-- ここに件数表示を追加する -->
</template>

確認方法

  1. タスクを2〜3件追加する
  2. チェックボックスをクリックすると打ち消し線がつく
  3. 「削除」ボタンで消える
  4. 件数が正しく更新される

★ 余裕がある人向け: 拡張課題

このドリルが PASS したら、以下の発展課題に挑戦してみてください (採点はなし、自由演習)。

  • localStorage にタスクを保存して、ページをリロードしてもタスクが残るようにする (watchtasks を JSON 化)
  • 「完了したタスクをすべて削除」ボタンを追加して、tasks.value = tasks.value.filter(...) の感覚をつかむ
  • 完了済みタイトルの文字色を CSS transition でフェードさせてみる (UX 改善)

採点ロジックは元のテストのままです。拡張部分を実装してもテストは PASS のまま、自分の力試しになります。

ドリル 2: ヒント

ドリル名: 02-add-task-app

> ⚠️ 自力で考えてから読んでください

> {{HINT}}

ドリル 3: v-model + push + splice + v-for - タスク管理アプリを白紙から

ドリル名: 03-build-task-app

> 🛠️ 【タイプ: ゼロベース実装問題】 ほぼ空の App.vue から仕様を読んで自力で実装してください。<template> 構造・id/class・内部ロジックは完全に自由。ただしテストが見る 画面の表示テキスト・ボタンの動作 は仕様どおりに揃えること。

問題

タスク管理アプリを Vue で 白紙から 実装してください。

App.vue には最小スケルトンしかありません。<template> 構造・id/class・関数名・内部ロジックはすべて自由ですが、下記の画面仕様を満たすこと。

仕様

画面要件

  • テキスト入力欄 (空文字でない値が入力できる)
  • 「追加」ボタン — クリックすると入力欄の値を新しいタスクとしてリストに追加
  • タスク一覧表示 — 追加されたタスクのタイトルを画面に表示
  • 「削除」ボタン (各タスクごと) — クリックすると該当タスクをリストから削除

初期状態

  • タスク一覧は空
  • 入力欄も空

動作

操作期待される結果
入力欄に「牛乳」と入力 → 「追加」クリック一覧に「牛乳」が表示される
さらに「卵」を追加一覧に「牛乳」「卵」の2件が表示される
「牛乳」の「削除」ボタンクリック一覧から「牛乳」が消え、「卵」だけ残る
空文字で「追加」クリック何も追加されない (バリデーション)

確認方法

  • 起動.bat (Windows) / 起動.sh (Mac) をダブルクリック
  • ブラウザにテスト結果が表示される

自由に設計できる例

  • <input>v-modelref に紐付ける / @input で手動取得する
  • タスクを ref<string[]> で持つ / ref<{ id, text }[]> で持つ
  • 削除を splice / filter / findIndex+splice で実装
  • ボタンに class や id を付けるか付けないかも自由

守る (テストが見ている)

  • 「追加」「削除」というテキストのボタンが画面にある
  • <input type="text"> がある
  • 追加・削除の動作が仕様どおり

このドリルで学ぶこと

学習要素学習資料との対応
v-model で input とリアクティブ変数を双方向バインドch05 学習目標
tasks.value.push() で配列に追加ch05 学習目標
tasks.value.splice(index, 1) で配列から削除ch05 slide「リストから項目を削除する」
フォーム送信時のバリデーション + リセットch05 学習目標
v-for でリストレンダリングch03-v-for

このドリルは 「ユーザー操作 → state 変化 → 画面再描画」のリアクティビティを1人で完結させる 経験。Vue の核心です。

ドリル 3: ヒント

ドリル名: 03-build-task-app

> ⚠️ 自力で考えてから読んでください

段階1: データを保持する

import { ref } from 'vue'
const tasks = ref([])           // タスク一覧
const newTask = ref('')         // 入力欄の値

段階2: 入力欄を作る

<input v-model="newTask" type="text" />

段階3: 追加ボタン

<button @click="addTask">追加</button>
function addTask() {
  // 空文字チェック
  // tasks.value.push する
  // newTask.value をクリア
}

段階4: 一覧表示

<ul>
  <li v-for="(task, i) in tasks" :key="i">
    {{ task }}
    <button @click="removeTask(i)">削除</button>
  </li>
</ul>

段階5: 削除

function removeTask(index) {
  tasks.value.splice(index, 1)
}

考えてみよう

既定では正解・不正解の判定はしません。自分のペースで「答えを見る」を。採点してほしい場合だけ「採点してほしい」を押してください。

ch05-list-form 演習ガイド

受講生向け演習問題集

採点の仕組み (読んでから始めてください)

採点テストは 「お題の通りに動くか」 を機械的にチェックします。

ドリルごとに 3つのタイプ があるので、各ドリル冒頭の 【タイプ】 を確認してください。

タイプ fill (穴埋め問題) — 文法・APIを知っているか

  • ___ (3個並んだアンダースコア) の空欄を埋める
  • 既存の <template> のテキストやボタン順は そのまま
  • ロジック自由度: 低 (お題どおりに直す)

タイプ add (機能追加問題) — 既存コードに機能を足せるか

  • 既存コードを残したまま、新しい機能を 追加 する
  • 実装方法は自由 (関数名・変数名・内部ロジックは好きに)
  • ただしテストが要求する見た目 (表示テキスト・ボタンの並び順) は守る
  • 既存テストを壊さないこと

タイプ build (ゼロベース実装問題) — 仕様から実装を組み立てられるか

  • ほぼ空の App.vue から 自力で全部書く
  • <template> 構造・id/class・内部ロジック: 完全に自由
  • 守るのは画面の機能仕様だけ: 表示するテキスト・ボタンの操作で何が起きるか
  • 一番ハードだが、実装力が一番つくタイプ

3タイプ共通で守ること

  • ファイル名 (App.vue) は変えない
  • tests/ フォルダは触らない (採点ロジック)
  • テンプレートの 表示テキスト (「カウント:」等) はテストが見ているので残す

このガイドのドリル一覧

#ドリル名タイトルタイプ難易度
101-fill-task-appv-model + push - フォーム入力でリスト追加🔧 穴埋め
202-add-task-appv-model + バリデーション - タスク追加機能の拡張➕ 機能追加
303-build-task-appv-model + push + splice + v-for - タスク管理アプリを白紙から🛠️ ゼロ実装★★

> 詰まったら ヒント ページを開く。それでも分からなければ講師に質問。

ドリル 1: v-model + push - フォーム入力でリスト追加

ドリル名: 01-fill-task-app

> 🔧 【タイプ: 穴埋め問題】 ___ の部分を埋めて完成させてください。既存のコード構造は変えないこと。

問題

___ を埋めて、入力したタスクを追加・削除できるアプリを完成させよ。___ は全部で5箇所ある。

<script setup>
import { ref } from 'vue'
const newTitle = ref('')
const tasks = ref([])

function addTask() {
  if (!newTitle.value.trim()) return
  tasks.value.___({ id: Date.now(), title: newTitle.value })   <!-- ___1: 配列末尾に追加するメソッド -->
  newTitle.value = ''
}

function removeTask(id) {
  tasks.value = tasks.value.___(t => t.id !== id)              <!-- ___2: 配列から条件で除外するメソッド -->
}
</script>

<template>
  <input v-___="newTitle" placeholder="タスク名" />              <!-- ___3: 双方向バインディング -->
  <button @click="addTask">追加</button>

  <ul>
    <li v-___="task in tasks" :___="task.id">                  <!-- ___4: 配列ループ / ___5: 識別キー -->
      {{ task.title }}
      <button @click="removeTask(task.id)">削除</button>
    </li>
  </ul>
</template>

確認方法

  1. App.vue に貼り付けて npm run dev で起動
  2. テキストを入力→「追加」でリストに表示される
  3. 「削除」ボタンで該当タスクが消える
  4. F12 コンソールに警告が出ないこと

ドリル 1: ヒント

ドリル名: 01-fill-task-app

> ⚠️ 自力で考えてから読んでください

  • ___1: 配列末尾に要素を追加する JavaScript の配列メソッド
  • ___2: 配列から条件に合う要素だけを残す JavaScript の配列メソッド
  • ___3 / ___4: Vue のディレクティブは v-モデル / v-繰り返し
  • ___5: v-for の各要素を識別するための属性(: から始まる)

ドリル 2: v-model + バリデーション - タスク追加機能の拡張

ドリル名: 02-add-task-app

> ➕ 【タイプ: 機能追加問題】 既存コードを残したまま新しい機能を追加してください。実装は自由ですが、テストが要求する I/F・表示テキストは守ること。

ベースコード(base/App.vue)は「追加」だけ動く状態。以下の機能を追加せよ。

追加する機能

  1. 完了切り替え: チェックボックスをクリックすると done が反転して、タイトルに打ち消し線がつく
  2. 削除: 「削除」ボタンをクリックすると該当タスクがリストから消える
  3. 件数表示: 「未完了 X 件 / 全 Y 件」を画面下部に表示する

ベースコード(base/App.vue)

<script setup>
import { ref } from 'vue'
const newTitle = ref('')
const tasks = ref([])

function addTask() {
  if (!newTitle.value.trim()) return
  tasks.value.push({ id: Date.now(), title: newTitle.value, done: false })
  newTitle.value = ''
}
// ここに toggleDone / deleteTask を追加する
</script>

<template>
  <h1>タスク一覧</h1>
  <input v-model="newTitle" placeholder="タスク名を入力" />
  <button @click="addTask">追加</button>
  <ul>
    <li v-for="task in tasks" :key="task.id">
      <!-- ここに完了切り替え・削除を追加する -->
      {{ task.title }}
    </li>
  </ul>
  <!-- ここに件数表示を追加する -->
</template>

確認方法

  1. タスクを2〜3件追加する
  2. チェックボックスをクリックすると打ち消し線がつく
  3. 「削除」ボタンで消える
  4. 件数が正しく更新される

★ 余裕がある人向け: 拡張課題

このドリルが PASS したら、以下の発展課題に挑戦してみてください (採点はなし、自由演習)。

  • localStorage にタスクを保存して、ページをリロードしてもタスクが残るようにする (watchtasks を JSON 化)
  • 「完了したタスクをすべて削除」ボタンを追加して、tasks.value = tasks.value.filter(...) の感覚をつかむ
  • 完了済みタイトルの文字色を CSS transition でフェードさせてみる (UX 改善)

採点ロジックは元のテストのままです。拡張部分を実装してもテストは PASS のまま、自分の力試しになります。

ドリル 2: ヒント

ドリル名: 02-add-task-app

> ⚠️ 自力で考えてから読んでください

> {{HINT}}

ドリル 3: v-model + push + splice + v-for - タスク管理アプリを白紙から

ドリル名: 03-build-task-app

> 🛠️ 【タイプ: ゼロベース実装問題】 ほぼ空の App.vue から仕様を読んで自力で実装してください。<template> 構造・id/class・内部ロジックは完全に自由。ただしテストが見る 画面の表示テキスト・ボタンの動作 は仕様どおりに揃えること。

問題

タスク管理アプリを Vue で 白紙から 実装してください。

App.vue には最小スケルトンしかありません。<template> 構造・id/class・関数名・内部ロジックはすべて自由ですが、下記の画面仕様を満たすこと。

仕様

画面要件

  • テキスト入力欄 (空文字でない値が入力できる)
  • 「追加」ボタン — クリックすると入力欄の値を新しいタスクとしてリストに追加
  • タスク一覧表示 — 追加されたタスクのタイトルを画面に表示
  • 「削除」ボタン (各タスクごと) — クリックすると該当タスクをリストから削除

初期状態

  • タスク一覧は空
  • 入力欄も空

動作

操作期待される結果
入力欄に「牛乳」と入力 → 「追加」クリック一覧に「牛乳」が表示される
さらに「卵」を追加一覧に「牛乳」「卵」の2件が表示される
「牛乳」の「削除」ボタンクリック一覧から「牛乳」が消え、「卵」だけ残る
空文字で「追加」クリック何も追加されない (バリデーション)

確認方法

  • 起動.bat (Windows) / 起動.sh (Mac) をダブルクリック
  • ブラウザにテスト結果が表示される

自由に設計できる例

  • <input>v-modelref に紐付ける / @input で手動取得する
  • タスクを ref<string[]> で持つ / ref<{ id, text }[]> で持つ
  • 削除を splice / filter / findIndex+splice で実装
  • ボタンに class や id を付けるか付けないかも自由

守る (テストが見ている)

  • 「追加」「削除」というテキストのボタンが画面にある
  • <input type="text"> がある
  • 追加・削除の動作が仕様どおり

このドリルで学ぶこと

学習要素学習資料との対応
v-model で input とリアクティブ変数を双方向バインドch05 学習目標
tasks.value.push() で配列に追加ch05 学習目標
tasks.value.splice(index, 1) で配列から削除ch05 slide「リストから項目を削除する」
フォーム送信時のバリデーション + リセットch05 学習目標
v-for でリストレンダリングch03-v-for

このドリルは 「ユーザー操作 → state 変化 → 画面再描画」のリアクティビティを1人で完結させる 経験。Vue の核心です。

ドリル 3: ヒント

ドリル名: 03-build-task-app

> ⚠️ 自力で考えてから読んでください

段階1: データを保持する

import { ref } from 'vue'
const tasks = ref([])           // タスク一覧
const newTask = ref('')         // 入力欄の値

段階2: 入力欄を作る

<input v-model="newTask" type="text" />

段階3: 追加ボタン

<button @click="addTask">追加</button>
function addTask() {
  // 空文字チェック
  // tasks.value.push する
  // newTask.value をクリア
}

段階4: 一覧表示

<ul>
  <li v-for="(task, i) in tasks" :key="i">
    {{ task }}
    <button @click="removeTask(i)">削除</button>
  </li>
</ul>

段階5: 削除

function removeTask(index) {
  tasks.value.splice(index, 1)
}

ch05-list-form 演習解説

⚠️ 自力でドリルを解いてから読んでください

ドリル 1: v-model + push - フォーム入力でリスト追加 — 模範解答

ドリル名: 01-fill-task-app

<script setup>
import { ref } from 'vue'
const newTitle = ref('')
const tasks = ref([])

function addTask() {
  if (!newTitle.value.trim()) return
  tasks.value.push({ id: Date.now(), title: newTitle.value })
  newTitle.value = ''
}

function removeTask(id) {
  tasks.value = tasks.value.filter(t => t.id !== id)
}
</script>

<template>
  <input v-model="newTitle" placeholder="タスク名" />
  <button @click="addTask">追加</button>

  <ul>
    <li v-for="task in tasks" :key="task.id">
      {{ task.title }}
      <button @click="removeTask(task.id)">削除</button>
    </li>
  </ul>
</template>

ドリル 1: v-model + push - フォーム入力でリスト追加 — 解説

ドリル名: 01-fill-task-app

空欄解答
___1push
___2filter
___3modelv-model
___4forv-for
___5key:key

観察ポイント

  • ___1splice などで書いた受講生は配列メソッドの理解度確認。動けば OK
  • ___5 を忘れた場合 F12 コンソールに警告が出るので「警告を読む」習慣の指導機会
  • removeTask(id) の id ベース filter は安全。splice(index, 1) でも動くが配列インデックスの不整合事故を経験させる材料に

ドリル 2: v-model + バリデーション - タスク追加機能の拡張 — 模範解答

ドリル名: 02-add-task-app

<script setup>
import { ref, computed } from 'vue'
const newTitle = ref('')
const tasks = ref([])

function addTask() {
  if (!newTitle.value.trim()) return
  tasks.value.push({ id: Date.now(), title: newTitle.value, done: false })
  newTitle.value = ''
}

function toggleDone(task) {
  task.done = !task.done
}

function deleteTask(id) {
  tasks.value = tasks.value.filter(t => t.id !== id)
}

const doneCount = computed(() => tasks.value.filter(t => t.done).length)
</script>

<template>
  <h1>タスク一覧</h1>
  <input v-model="newTitle" placeholder="タスク名を入力" />
  <button @click="addTask">追加</button>
  <ul>
    <li v-for="task in tasks" :key="task.id">
      <input type="checkbox" :checked="task.done" @change="toggleDone(task)" />
      <span :style="{ textDecoration: task.done ? 'line-through' : 'none' }">
        {{ task.title }}
      </span>
      <button @click="deleteTask(task.id)">削除</button>
    </li>
  </ul>
  <p>未完了 {{ tasks.length - doneCount }} 件 / 全 {{ tasks.length }} 件</p>
</template>

ドリル 2: v-model + バリデーション - タスク追加機能の拡張 — 解説

ドリル名: 02-add-task-app

{{ANSWER_CODE}}

ドリル 3: v-model + push + splice + v-for - タスク管理アプリを白紙から — 模範解答

ドリル名: 03-build-task-app

<script setup>
import { ref } from 'vue'

const newTask = ref('')
const tasks = ref([])

function addTask() {
  const trimmed = newTask.value.trim()
  if (!trimmed) return
  tasks.value.push(trimmed)
  newTask.value = ''
}

function removeTask(index) {
  tasks.value.splice(index, 1)
}
</script>

<template>
  <div>
    <input v-model="newTask" type="text" placeholder="タスクを入力" />
    <button @click="addTask">追加</button>
    <ul>
      <li v-for="(task, i) in tasks" :key="i">
        {{ task }}
        <button @click="removeTask(i)">削除</button>
      </li>
    </ul>
  </div>
</template>

ドリル 3: v-model + push + splice + v-for - タスク管理アプリを白紙から — 解説

ドリル名: 03-build-task-app

Vue でゼロから書く思考プロセス

  1. データを ref で持つ — リアクティブにする
  2. <input> を v-model で双方向バインド — 入力値を取り出す
  3. イベントハンドラ@click="addTask" のように関数を呼ぶ
  4. v-for で配列をレンダリング — タスク一覧を出す
  5. key 属性 — 各要素を一意に識別 (削除時のリアクティビティのため)

実装が複数あり得る点

  • データ型: ref<string[]> (シンプル) / ref<{ id, text }[]> (拡張しやすい)
  • 追加: push / [...tasks.value, newTask.value] (immutable)
  • 削除: splice / filter / findIndex + splice
  • key: index / id (タスクごと採番)
  • バリデーション: trim() で空白チェック / if (!val) だけ

テストは「画面の挙動」だけを見ているので、内部実装はどれでも OK。

ID/class について

このアプリでは id/class を付けても付けなくてもテストは通る。テストが要素を取得する方法:

  • <input type="text"> で input を見つける
  • <button> のテキスト「追加」「削除」で見つける
  • <li>findAll で取得

なので「追加」「削除」というボタンの 表示テキスト だけは固定する。

Q: タスクリスト(追加・削除)の空欄を埋める

問題

___ を埋めて、入力したタスクを追加・削除できるアプリを完成させよ。___ は全部で5箇所ある。

<script setup>
import { ref } from 'vue'
const newTitle = ref('')
const tasks = ref([])

function addTask() {
  if (!newTitle.value.trim()) return
  tasks.value.___({ id: Date.now(), title: newTitle.value })   <!-- ___1: 配列末尾に追加するメソッド -->
  newTitle.value = ''
}

function removeTask(id) {
  tasks.value = tasks.value.___(t => t.id !== id)              <!-- ___2: 配列から条件で除外するメソッド -->
}
</script>

<template>
  <input v-___="newTitle" placeholder="タスク名" />              <!-- ___3: 双方向バインディング -->
  <button @click="addTask">追加</button>

  <ul>
    <li v-___="task in tasks" :___="task.id">                  <!-- ___4: 配列ループ / ___5: 識別キー -->
      {{ task.title }}
      <button @click="removeTask(task.id)">削除</button>
    </li>
  </ul>
</template>

確認方法

  1. App.vue に貼り付けて npm run dev で起動
  2. テキストを入力→「追加」でリストに表示される
  3. 「削除」ボタンで該当タスクが消える
  4. F12 コンソールに警告が出ないこと

ヒント

  • ___1: 配列末尾に要素を追加する JavaScript の配列メソッド
  • ___2: 配列から条件に合う要素だけを残す JavaScript の配列メソッド
  • ___3 / ___4: Vue のディレクティブは v-モデル / v-繰り返し
  • ___5: v-for の各要素を識別するための属性(: から始まる)

演習: タスクアプリを完成させる

ベースコード(base/App.vue)は「追加」だけ動く状態。以下の機能を追加せよ。

追加する機能

  1. 完了切り替え: チェックボックスをクリックすると done が反転して、タイトルに打ち消し線がつく
  2. 削除: 「削除」ボタンをクリックすると該当タスクがリストから消える
  3. 件数表示: 「未完了 X 件 / 全 Y 件」を画面下部に表示する

ベースコード(base/App.vue)

<script setup>
import { ref } from 'vue'
const newTitle = ref('')
const tasks = ref([])

function addTask() {
  if (!newTitle.value.trim()) return
  tasks.value.push({ id: Date.now(), title: newTitle.value, done: false })
  newTitle.value = ''
}
// ここに toggleDone / deleteTask を追加する
</script>

<template>
  <h1>タスク一覧</h1>
  <input v-model="newTitle" placeholder="タスク名を入力" />
  <button @click="addTask">追加</button>
  <ul>
    <li v-for="task in tasks" :key="task.id">
      <!-- ここに完了切り替え・削除を追加する -->
      {{ task.title }}
    </li>
  </ul>
  <!-- ここに件数表示を追加する -->
</template>

確認方法

  1. タスクを2〜3件追加する
  2. チェックボックスをクリックすると打ち消し線がつく
  3. 「削除」ボタンで消える
  4. 件数が正しく更新される

★ 余裕がある人向け: 拡張課題

このドリルが PASS したら、以下の発展課題に挑戦してみてください (採点はなし、自由演習)。

  • localStorage にタスクを保存して、ページをリロードしてもタスクが残るようにする (watchtasks を JSON 化)
  • 「完了したタスクをすべて削除」ボタンを追加して、tasks.value = tasks.value.filter(...) の感覚をつかむ
  • 完了済みタイトルの文字色を CSS transition でフェードさせてみる (UX 改善)

採点ロジックは元のテストのままです。拡張部分を実装してもテストは PASS のまま、自分の力試しになります。

> {{HINT}}

解答

{{ANSWER_CODE}}

Q: タスク管理アプリを ゼロから実装する

問題

タスク管理アプリを Vue で 白紙から 実装してください。

App.vue には最小スケルトンしかありません。<template> 構造・id/class・関数名・内部ロジックはすべて自由ですが、下記の画面仕様を満たすこと。

仕様

画面要件

  • テキスト入力欄 (空文字でない値が入力できる)
  • 「追加」ボタン — クリックすると入力欄の値を新しいタスクとしてリストに追加
  • タスク一覧表示 — 追加されたタスクのタイトルを画面に表示
  • 「削除」ボタン (各タスクごと) — クリックすると該当タスクをリストから削除

初期状態

  • タスク一覧は空
  • 入力欄も空

動作

操作期待される結果
入力欄に「牛乳」と入力 → 「追加」クリック一覧に「牛乳」が表示される
さらに「卵」を追加一覧に「牛乳」「卵」の2件が表示される
「牛乳」の「削除」ボタンクリック一覧から「牛乳」が消え、「卵」だけ残る
空文字で「追加」クリック何も追加されない (バリデーション)

確認方法

  • 起動.bat (Windows) / 起動.sh (Mac) をダブルクリック
  • ブラウザにテスト結果が表示される

自由に設計できる例

  • <input>v-modelref に紐付ける / @input で手動取得する
  • タスクを ref<string[]> で持つ / ref<{ id, text }[]> で持つ
  • 削除を splice / filter / findIndex+splice で実装
  • ボタンに class や id を付けるか付けないかも自由

守る (テストが見ている)

  • 「追加」「削除」というテキストのボタンが画面にある
  • <input type="text"> がある
  • 追加・削除の動作が仕様どおり

このドリルで学ぶこと

学習要素学習資料との対応
v-model で input とリアクティブ変数を双方向バインドch05 学習目標
tasks.value.push() で配列に追加ch05 学習目標
tasks.value.splice(index, 1) で配列から削除ch05 slide「リストから項目を削除する」
フォーム送信時のバリデーション + リセットch05 学習目標
v-for でリストレンダリングch03-v-for

このドリルは 「ユーザー操作 → state 変化 → 画面再描画」のリアクティビティを1人で完結させる 経験。Vue の核心です。

ヒント

段階1: データを保持する

import { ref } from 'vue'
const tasks = ref([])           // タスク一覧
const newTask = ref('')         // 入力欄の値

段階2: 入力欄を作る

<input v-model="newTask" type="text" />

段階3: 追加ボタン

<button @click="addTask">追加</button>
function addTask() {
  // 空文字チェック
  // tasks.value.push する
  // newTask.value をクリア
}

段階4: 一覧表示

<ul>
  <li v-for="(task, i) in tasks" :key="i">
    {{ task }}
    <button @click="removeTask(i)">削除</button>
  </li>
</ul>

段階5: 削除

function removeTask(index) {
  tasks.value.splice(index, 1)
}

解説

Vue でゼロから書く思考プロセス

  1. データを ref で持つ — リアクティブにする
  2. <input> を v-model で双方向バインド — 入力値を取り出す
  3. イベントハンドラ@click="addTask" のように関数を呼ぶ
  4. v-for で配列をレンダリング — タスク一覧を出す
  5. key 属性 — 各要素を一意に識別 (削除時のリアクティビティのため)

実装が複数あり得る点

  • データ型: ref<string[]> (シンプル) / ref<{ id, text }[]> (拡張しやすい)
  • 追加: push / [...tasks.value, newTask.value] (immutable)
  • 削除: splice / filter / findIndex + splice
  • key: index / id (タスクごと採番)
  • バリデーション: trim() で空白チェック / if (!val) だけ

テストは「画面の挙動」だけを見ているので、内部実装はどれでも OK。

ID/class について

このアプリでは id/class を付けても付けなくてもテストは通る。テストが要素を取得する方法:

  • <input type="text"> で input を見つける
  • <button> のテキスト「追加」「削除」で見つける
  • <li>findAll で取得

なので「追加」「削除」というボタンの 表示テキスト だけは固定する。