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

Vue — 動く画面をつくる

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

Vue — 動く画面をつくる

ref → v-if/v-for → コンポーネント分割

1. まず動かしてみる

今日のゴール

このトピックが終わると、ボタンを押すと数字が増えるアプリを自分で作れる。

でもその前に——まず画面に文字を出すところから始める。

> 説明は後回し。先に手を動かして「動いた」を体験しよう。

はじめての Vue:文字を出す

App.vue をこの全文に書き換えて、保存しよう。

<script setup>
</script>

<template>
  <h1>Hello Vue</h1>
</template>

> これで全部。<script setup> は空でいい。

動かす

npm run dev

ブラウザで http://localhost:5173 を開くと——

*(図は省略)*

画面に「Hello Vue」が出た。 これがあなたの最初の Vue アプリ。

今、何が起きたか

<template>
  <h1>Hello Vue</h1>
</template>
  • <template> の中に書いた HTML が、そのまま画面に出る
  • まだ JavaScript(<script>)は空っぽ。HTML を書いただけ

> Vue は「<template> に書いた中身を画面に出す」のが基本。まずはこれだけ。

次は「変数の中身」を出す

文字を直接書くのではなく、変数に入れてから出してみる。

<script setup>
const message = 'Hello Vue'
</script>

<template>
  <h1>{{ message }}</h1>
</template>

> {{ }} で囲むと、変数の中身を画面に出せる。

{{ }} の意味

*(図は省略)*

> {{ message }} は「message の中身をここに出して」という意味。

ここまでのまとめ

やったことコード
文字を画面に出した<h1>Hello Vue</h1>
変数を作ったconst message = '...'
変数の中身を出した{{ message }}

> ここまでは「表示するだけ」。次は「変えると画面も変わる」に進む。

2. ボタンで変えてみる(まず失敗する)

数字をボタンで増やしたい

「ボタンを押すと数字が増える」を作りたい。素直に書くとこうなる。

<script setup>
let count = 0
function increment() {
  count = count + 1
}
</script>

<template>
  <p>カウント: {{ count }}</p>
  <button @click="increment">増やす</button>
</template>

> このコードをコピペして、保存して、ボタンを押してみよう。

押しても…変わらない

*(図は省略)*

ボタンは効いているのに、画面の数字が変わらない。 なぜ?

理由:Vue は let の変化に気づけない

let count = 0      // ← ただの変数
count = count + 1  // ← 値は変わっている

count の値は確かに増えている。でも Vue は「count が変わった」ことを知らない。だから画面を更新しない。

> Vue に「この変数を見張っていて」と伝える必要がある。それが ref

3. ref を使って直す

letref に変える

さっきのコードを ref を使って直す。全文はこう。

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

const count = ref(0)
function increment() {
  count.value = count.value + 1
}
</script>

<template>
  <p>カウント: {{ count }}</p>
  <button @click="increment">増やす</button>
</template>

> これをコピペして保存。今度はボタンを押すと…

今度は変わる!

*(図は省略)*

動いた。 変えたのは let count = 0const count = ref(0) にしただけ(と .value)。

3つだけ変えた

変更前変更後
(import なし)import { ref } from 'vue'
let count = 0const count = ref(0)
count = count + 1count.value = count.value + 1

> この3つで「画面が変わらない」が「変わる」になった。次でこの意味を説明する。

4. 動いたコードを読む

全文をもう一度

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

const count = ref(0)
function increment() {
  count.value = count.value + 1
}
</script>

<template>
  <p>カウント: {{ count }}</p>
  <button @click="increment">増やす</button>
</template>

これを上から1行ずつ見ていく。

① import { ref } from 'vue'

import { ref } from 'vue'
  • Vue から ref という道具を持ってくる宣言
  • これを書かないと ref is not defined というエラーになる

> 「ref を使うよ」という最初のおまじない。

② const count = ref(0)

const count = ref(0)
  • ref(0) で「中に 0 が入った、Vue が見張れる箱」を作る
  • それを count という名前で持っておく

*(図は省略)*

> ただの変数ではなく「Vue が見張れる箱」になったのがポイント。

③ count.value で中身をさわる

count.value = count.value + 1
  • 箱の中身は .value でアクセスする
  • 右の count.value は今の中身を読む
  • 左の count.value = は新しい中身を入れる

> count は箱、count.value が中身。script では .value を付ける。

④ {{ count }} で表示

<p>カウント: {{ count }}</p>
  • template では .value付けない
  • {{ count }} だけで、Vue が自動で中身を取り出して表示する

> script は .value あり、template は .value なし。これが今日の最重要ポイント。

⑤ @click="increment"

<button @click="increment">増やす</button>
  • @click は「クリックされたら」
  • その後に「呼びたい関数の名前」を書く
  • increment と名前だけ書く(increment() とは書かない)

> ボタンとやりたい処理を結びつけるのが @click

なぜ ref だと画面が変わるのか

*(図は省略)*

> ref は監視できる箱。let はただの変数で監視できない。この差が「変わる/変わらない」を分けた。

5. .value でつまずかないために

いちばん多い間違い

script で .value を忘れる。この全文はエラーになる。

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

const count = ref(0)
function increment() {
  count = count + 1   // ❌ .value がない
}
</script>

<template>
  <p>カウント: {{ count }}</p>
  <button @click="increment">増やす</button>
</template>

なぜエラーになるか

count = count + 1   // ❌
  • count は箱そのもの。箱を別のものに入れ替えようとしている
  • でも const なので入れ替えられない → エラー
  • 変えたいのは中身。だから count.value = ...

> 「箱」を変えるのではなく「箱の中身」を変える、と覚える。

正しい書き方(全文)

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

const count = ref(0)
function increment() {
  count.value = count.value + 1   // ✅ .value あり
}
</script>

<template>
  <p>カウント: {{ count }}</p>
  <button @click="increment">増やす</button>
</template>

> count.value と書けば中身をさわれる。

短く書く:count.value++

count.value = count.value + 1 は長い。同じことを短く書ける。

count.value++          // count.value = count.value + 1 と同じ
count.value += 5       // 5 増やす
count.value = 0        // 0 に戻す

> JavaScript の +++= がそのまま使える。

template では逆に .value を書かない

<template>
  <p>カウント: {{ count }}</p>       <!-- ✅ -->
  <p>カウント: {{ count.value }}</p>  <!-- ❌ 余計 -->
</template>
  • template では Vue が自動で中身を取り出す
  • だから .value は付けない

> script は付ける、template は付けない。逆なので混乱しやすい。

.value 早見表

場所.value
<script> の中付けるcount.value++
<template> の中付けない{{ count }}

*(図は省略)*

6. ボタンと関数

短い処理は @click に直接書ける

関数を作らなくても、短い処理なら @click に直接書ける。

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

<template>
  <p>カウント: {{ count }}</p>
  <button @click="count++">増やす</button>
</template>

> @click="count++" のように式を直接書ける。template では .value 不要なので count++ でOK。

ボタンを増やす(全文)

増やす・減らす・リセットの3ボタン。

<script setup>
import { ref } from 'vue'
const count = ref(0)

function reset() {
  count.value = 0
}
</script>

<template>
  <p>カウント: {{ count }}</p>
  <button @click="count++">増やす</button>
  <button @click="count--">減らす</button>
  <button @click="reset">リセット</button>
</template>

式と関数の使い分け

書き方いつ使う
@click="count++"処理が1行で済むとき
@click="reset"処理が複数行のとき(関数にする)

> 短い → その場に書く。長い → 関数にして名前を渡す。

❌ 関数に () を付けない

<button @click="reset">リセット</button>     <!-- ✅ -->
<button @click="reset()">リセット</button>   <!-- ❌ -->
  • reset → クリックされたら呼ぶ(正しい)
  • reset() → ページを開いた瞬間に1回実行されてしまう

> 関数は名前だけ渡す。() を付けると「今すぐ実行」の意味になる。

7. v-if で表示を出し分ける

条件によって表示を変えたい

「カウントが 0 のときだけメッセージを出す」をやりたい。v-if を使う。

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

<template>
  <p>カウント: {{ count }}</p>
  <p v-if="count === 0">まだ押されていません</p>
  <button @click="count++">増やす</button>
</template>

> v-if="条件" を付けた要素は、条件が true のときだけ表示される。

v-if / v-else-if / v-else

<template>
  <p v-if="count === 0">ゼロです</p>
  <p v-else-if="count < 0">マイナスです</p>
  <p v-else>プラスです</p>
</template>
  • v-if → 最初の条件
  • v-else-if → 次の条件(複数可)
  • v-else → どれにも当てはまらないとき

> JavaScript の if / else if / else と同じ感覚で使える。

v-if と v-show の違い

<p v-if="isOpen">v-if で出す</p>
<p v-show="isOpen">v-show で出す</p>
v-ifv-show
false のときDOM ごと消すCSS で隠す(DOM は残る)
切り替えコスト高い低い
使いどころめったに変わらない頻繁に切り替える

> 頻繁にオンオフするなら v-show、条件が固定的なら v-if

真偽値の ref と組み合わせる

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

<template>
  <button @click="isOpen = !isOpen">切り替え</button>
  <p v-if="isOpen">開いています</p>
  <p v-else>閉じています</p>
</template>

> ref(false)! で反転させれば、表示のオン・オフが作れる。

8. v-for でリストを表示する

配列を画面に並べる

「複数のデータを一覧表示」したい。v-for を使う。

<script setup>
import { ref } from 'vue'
const fruits = ref(['りんご', 'みかん', 'ぶどう'])
</script>

<template>
  <ul>
    <li v-for="fruit in fruits" :key="fruit">
      {{ fruit }}
    </li>
  </ul>
</template>

> v-for="要素 in 配列" で、配列の数だけ要素を繰り返す。

v-for の読み方

*(図は省略)*

> 「fruits の中身を1個ずつ fruit という名前で取り出す」という意味。

オブジェクトの配列を並べる

実務ではオブジェクトの配列を扱うことが多い。

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

<template>
  <ul>
    <li v-for="task in tasks" :key="task.id">
      {{ task.title }}
    </li>
  </ul>
</template>

> 取り出した task から task.title のようにプロパティを使える。

:key は必ず付ける

<li v-for="task in tasks" :key="task.id">
  • :key は各要素を区別するための目印
  • Vue が「どの要素がどれか」を正しく追跡できる
  • 一意な値(id など)を渡す

> :key を忘れると、並び替えや削除のときに表示がおかしくなる。

❌ index を key にしない

<!-- ❌ 並び替え・削除で崩れる -->
<li v-for="(item, i) in items" :key="i">

<!-- ✅ 一意な id を使う -->
<li v-for="item in items" :key="item.id">
  • index(順番)は、要素が増減するとズレる
  • だから id のような変わらない値を使う

> 「key には id」を習慣にする。

v-for で番号を振る

順番が欲しいときは index を受け取れる。

<template>
  <ol>
    <li v-for="(task, index) in tasks" :key="task.id">
      {{ index + 1 }}番目: {{ task.title }}
    </li>
  </ol>
</template>

> (要素, index) の形で、何番目かを受け取れる。表示用途なら OK(key には使わない)。

v-for と v-if を組み合わせる

「完了したタスクだけ表示」のように、繰り返し+条件を組む。

<template>
  <ul>
    <template v-for="task in tasks" :key="task.id">
      <li v-if="task.done">{{ task.title }}(完了)</li>
    </template>
  </ul>
</template>

> 同じ要素に v-forv-if直接書かない<template> で挟んで分離する。

リストに追加する

ボタンで配列に要素を足すと、画面も自動で増える。

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

function addTask() {
  tasks.value.push({ id: nextId++, title: `タスク${nextId}` })
}
</script>

<template>
  <button @click="addTask">追加</button>
  <ul>
    <li v-for="task in tasks" :key="task.id">{{ task.title }}</li>
  </ul>
</template>

> tasks.value.push(...) で配列に足すと、v-for が自動で1行増やす。これがリアクティビティ。

9. コンポーネントに分割する

部品に分けたい理由

1つの .vue が大きくなったら、部品(コンポーネント)に分ける

*(図は省略)*

> 「1部品 = 1つの役割」に分けると、再利用も修正も楽になる。

子コンポーネントを作る

components/TaskItem.vue を新規作成(全文)。

<script setup>
const props = defineProps({
  title: { type: String, required: true },
})
</script>

<template>
  <li class="task">{{ props.title }}</li>
</template>

<style scoped>
.task { padding: 8px; border-bottom: 1px solid #eee; }
</style>

> defineProps で「親から受け取る値」を宣言する。

props = 親から子へデータを渡す

*(図は省略)*

> 親が :title="..." で渡し、子が defineProps で受け取る。

親で子を使う(全文)

App.vue から子コンポーネントを呼ぶ。

<script setup>
import { ref } from 'vue'
import TaskItem from './components/TaskItem.vue'

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

<template>
  <ul>
    <TaskItem
      v-for="task in tasks"
      :key="task.id"
      :title="task.title"
    />
  </ul>
</template>

> import して <TaskItem> として使う。:title で props を渡す。

emit = 子から親へ通知する

子で起きたこと(クリックなど)を親に伝えるには emit を使う。

<!-- TaskItem.vue(子) -->
<script setup>
const props = defineProps({ title: String })
const emit = defineEmits(['remove'])
</script>

<template>
  <li>
    {{ props.title }}
    <button @click="emit('remove')">削除</button>
  </li>
</template>

> defineEmits でイベント名を宣言し、emit('名前') で親に通知する。

親で emit を受け取る(全文)

<script setup>
import { ref } from 'vue'
import TaskItem from './components/TaskItem.vue'

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

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

<template>
  <ul>
    <TaskItem
      v-for="task in tasks"
      :key="task.id"
      :title="task.title"
      @remove="removeTask(task.id)"
    />
  </ul>
</template>

> 子の emit('remove') を、親が @remove で受けて削除する。

props と emit の関係

*(図は省略)*

> データは親→子(props)、通知は子→親(emit)。この一方通行が Vue の鉄則。

10. 完成形

今日のゴール(全文)

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

const count = ref(0)

function reset() {
  count.value = 0
}
</script>

<template>
  <p>カウント: {{ count }}</p>
  <button @click="count++">増やす</button>
  <button @click="count--">減らす</button>
  <button @click="reset">リセット</button>
</template>

> このコードを自分で書けるようになるのが、今日の目標。

動かして確認

npm run dev
  1. http://localhost:5173 を開く
  2. 「増やす」「減らす」「リセット」を押す
  3. 数字が変わることを確認する
  4. F12 でコンソールにエラーが出ていないか見る

11. つまずいたら

画面が変わらないとき

順番に確認しよう。

  1. ref を使っているか?(let ではなく const ... = ref(...)
  2. script で .value を付けているか?(count.value++
  3. import を書いたか?(import { ref } from 'vue'

> この3つのどれかが抜けていることがほとんど。

エラーが出たとき

F12 を押してコンソールを見る。

*(図は省略)*

> エラーは「どこが悪いか」を教えてくれる。読めば直せる。

よくあるエラー

エラー / 症状原因直し方
ref is not definedimport 忘れimport { ref } from 'vue'
画面が変わらないlet を使っているref にする
画面が変わらない.value 漏れscript で .value を付ける
ボタンが即実行されるreset() と書いた() を外す

12. 用語集とまとめ

今日の用語(1/2)

用語意味
リアクティブ値が変わると画面が自動で追従すること
ref値を「Vue が見張れる箱」にする関数
.valueref の箱の中身。script でさわるときに付ける
{{ }}template で変数の中身を表示する書き方
@clickクリックされたときの処理を指定するイベント

今日の用語(2/2)

用語意味
v-if / v-else条件によって表示を出し分ける
v-showCSS で表示/非表示を切り替える(DOM は残る)
v-for配列を繰り返して要素を並べる
:keyv-for の各要素を区別する目印(一意な id)
defineProps親から子へデータを受け取る宣言(props)
defineEmits子から親へ通知するイベントの宣言(emit)

いちばん大事な1枚

*(図は省略)*

> 迷ったらこれを思い出す。

まとめ

やったことコード
リアクティブな変数を作ったconst count = ref(0)
script で値を変えたcount.value++
template で表示した{{ count }}
表示を出し分けたv-if="count === 0"
リストを並べたv-for="t in tasks" :key="t.id"
部品に分けたdefineProps / defineEmits

13. 理解度チェック

Q1. 出力を予想する

このボタンを3回押すと、画面の数字は?

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

<template>
  <p>カウント: {{ count }}</p>
  <button @click="count++">増やす</button>
</template>

Q1. 答え

カウント: 3

  • 初期値 0 から count++ で 1 ずつ増える
  • 0 → 1 → 2 → 3

Q2. 誤りを見つける

この全文はエラーになる。どこが悪い?

<script setup>
import { ref } from 'vue'
const count = ref(0)
function increment() {
  count++
}
</script>

<template>
  <button @click="increment">増やす</button>
</template>

Q2. 答え

count++ が誤り。正しくは count.value++

  • count は箱そのもの。script では .value を付けて中身をさわる

Q3. .value は要る?

(A) と (B)、それぞれ .value が必要か?

<script setup>
function f() {
  return count___      // (A) script の中
}
</script>

<template>
  <p>{{ count___ }}</p> <!-- (B) template の中 -->
</template>

Q3. 答え

箇所場所.value
(A)script必要count.value
(B)template不要count

Q4. ボタンが効かない理由

押しても何も起きない。なぜ?

<button @click="reset()">リセット</button>

Q4. 答え

reset() だと、ページを開いた瞬間に実行されてしまう

  • 正: <button @click="reset">リセット</button>() なし)

Q5. :key に何を渡す?

このリストの :key には何を渡すべき? なぜ index ではダメ?

<li v-for="task in tasks" :key="???">
  {{ task.title }}
</li>

Q5. 答え

一意な値(task.id)を渡す。index はダメ。

  • :key="task.id" が正解
  • index は要素の追加・削除・並び替えでズレる
  • ズレると Vue が要素を取り違えて表示が崩れる

Q6. props と emit の向き

親子コンポーネント間で、データと通知はどちら向き?

Q6. 答え

向き仕組み
親 → 子(データ)props:title="..." / defineProps
子 → 親(通知)emit@remove="..." / defineEmits

> データは下りる、通知は上る。この一方通行が Vue の鉄則。

演習に進む前のチェック

  • [ ] let ではなく ref を使う理由が言える
  • [ ] script と template で .value の有無が言える
  • [ ] @click で関数を () なしで渡せる
  • [ ] v-if で表示を出し分けられる
  • [ ] v-for でリストを並べ、:key に id を渡せる
  • [ ] props(親→子)と emit(子→親)の向きが言える

> 1つでも怪しければ、該当セクションに戻ろう。

手を動かす番です

演習ガイドを開いて、ドリル01から始めよう

ハンズオン

ch02-reactivity 演習ガイド

受講生向け演習問題集

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

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

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

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

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

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

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

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

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

3タイプ共通で守ること

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

このガイドのドリル一覧

#ドリル名タイトルタイプ難易度
101-fill-counterref + クリックハンドラ - カウンター実装🔧 穴埋め
202-fill-refref().value - リアクティブ変数の取り扱い🔧 穴埋め
303-add-stepステップ加算と偶奇表示 - カウンターに機能を足す➕ 機能追加
404-build-toggleON/OFF スイッチ - ref を使って白紙から組み立てる🛠️ ゼロ実装
505-add-vifv-if で状態メッセージを出し分ける➕ 機能追加★★
606-fill-vforv-for でタスク一覧を表示する🔧 穴埋め★★
707-build-componentコンポーネント分割 - props と emit でタスクリスト🛠️ ゼロ実装★★★

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

ドリル 1: ref + クリックハンドラ - カウンター実装

ドリル名: 01-fill-counter

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

問題

___ を埋めて、増やす・減らす・リセットができるカウンターを完成させよ。___ は全部で5箇所ある。

<script setup>
import { ___ } from 'vue'        <!-- ___1: ref をインポートする関数名 -->
const count = ___(0)             <!-- ___2: 初期値0のリアクティブ変数を作る -->

function reset() {
  count.___ = 0                  <!-- ___3: script 内で値を書き換えるときの構文 -->
}
</script>

<template>
  <p>カウント: {{ count }}</p>
  <button @___="count++">増やす</button>   <!-- ___4: クリックイベントの記号 -->
  <button @click="count--">減らす</button>
  <button @click="___">リセット</button>   <!-- ___5: 関数を呼ぶ書き方 -->
</template>

確認方法

  1. App.vue に貼り付けて npm run dev で起動する
  2. 「増やす」「減らす」「リセット」がすべて動くこと
  3. F12 コンソールにエラーが出ないこと

ドリル 1: ヒント

ドリル名: 01-fill-counter

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

  • ___1 / ___2: manuscript.md の「ref()」スライド参照
  • ___3: manuscript.md の「.value の使い分け」スライド参照(script 内では .value が必要)
  • ___4: クリックイベントは @click
  • ___5: 関数を渡すときは () を付けない(@click="reset"

ドリル 2: ref().value - リアクティブ変数の取り扱い

ドリル名: 02-fill-ref

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

問題

___ を埋めて、ボタンをクリックするとカウントが増えるアプリを完成させよ。___ は全部で3箇所ある。

<script setup>
import { ___ } from 'vue'        <!-- ___1: ref をインポートする関数名 -->
const count = ___(0)             <!-- ___2: 初期値0のリアクティブ変数を作る -->
</script>

<template>
  <p>カウント: {{ count }}</p>
  <button @click="___++">増やす</button>   <!-- ___3: テンプレートで変数を参照する名前 -->
</template>

確認方法

  1. App.vue に貼り付けて npm run dev で起動する
  2. 「増やす」ボタンをクリックするたびに数字が増えること

ドリル 2: ヒント

ドリル名: 02-fill-ref

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

  • ___1: Vue からリアクティブ変数を作る関数。import { ??? } from 'vue'
  • ___2: ref() に初期値を渡すと、リアクティブな変数が返る
  • ___3: テンプレートの中では .value を付けずに変数名だけで参照できる

ドリル 3: ステップ加算と偶奇表示 - カウンターに機能を足す

ドリル名: 03-add-step

> ➕ 【タイプ: 機能追加問題】 既存コードを残したまま、新しい機能を 追加 してください。実装方法は自由ですが、表示テキスト (カウント: 偶数 奇数) は守ること。

問題

「増やす/減らす」だけのカウンターに、2つの機能を 追加 せよ。

  1. 「+5」ボタン: クリックで count が 5 増える
  2. 偶奇の表示: count が偶数なら 偶数、奇数なら 奇数<p> で表示する
<template>
  <p>カウント: {{ count }}</p>
  <button @click="count++">増やす</button>
  <button @click="count--">減らす</button>
  <!-- 「+5」ボタンを追加 -->
  <!-- 偶数/奇数を表示する <p> を追加 -->
</template>

確認方法

  1. App.vue に貼り付けて npm run dev で起動する
  2. 「+5」で 5 ずつ増えること
  3. count が変わると「偶数/奇数」が切り替わること

ドリル 3: ヒント

ドリル名: 03-add-step

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

  • 「+5」は既存ボタンと同じ。@click="count += 5" でよい
  • 偶奇は count % 2 === 0 なら偶数
  • テンプレートで条件を出すには三項演算子: {{ 条件 ? 'A' : 'B' }}computed はまだ不要)

ドリル 4: ON/OFF スイッチ - ref を使って白紙から組み立てる

ドリル名: 04-build-toggle

> 🛠️ 【タイプ: ゼロ実装問題】 ほぼ空の App.vue から自力で全部書きます。<template> 構造・命名は自由。守るのは画面に出る文字だけ。

問題

次の仕様を満たすスイッチを 白紙から 作れ。

  1. 状態表示: ON なら 状態: ON、OFF なら 状態: OFF(初期は OFF
  2. 「切り替え」ボタン: 押すたびに ON ↔ OFF が反転する
  3. 押した回数表示: 押した回数: 0 から始まり、押すたびに 1 増える

守るのは画面の文字 (状態: ON / 状態: OFF / 押した回数: N) だけ。

確認方法

  1. npm run dev で起動する
  2. 初期表示が 状態: OFF / 押した回数: 0
  3. ボタンを押すと ONOFF が交互に変わり、回数が増えること

ドリル 4: ヒント

ドリル名: 04-build-toggle

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

  • 状態は真偽値の ref: const isOn = ref(false)、反転は isOn.value = !isOn.value
  • 回数も数値の ref: const taps = ref(0)、押すたびに taps.value++
  • 1つの関数の中で「反転」と「+1」を両方やってしまえばよい
  • 表示の出し分けは {{ isOn ? 'ON' : 'OFF' }}

ドリル 5: v-if で状態メッセージを出し分ける

ドリル名: 05-add-vif

> ➕ 【タイプ: 機能追加問題】 既存のカウンターを残したまま、状態メッセージを追加してください。表示テキスト (ゼロです マイナスです プラスです) は守ること。

問題

カウンターに、count の値に応じた状態メッセージを v-if / v-else-if / v-else で追加せよ。

条件表示
0ゼロです
マイナスマイナスです
プラスプラスです

3つのうち同時に1つだけ表示されること。

ドリル 5: ヒント

ドリル名: 05-add-vif

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

  • v-if="条件"v-else-if="条件"v-else の順に書く
  • 条件は式: count === 0 / count < 0
  • v-else には条件を書かない(「それ以外」)
  • template なので count.value は不要

ドリル 6: v-for でタスク一覧を表示する

ドリル名: 06-fill-vfor

> 🔧 【タイプ: 穴埋め問題】 ___ を埋めて、タスク配列を一覧表示し「追加」で増えるアプリを完成させてください。

問題

___ を埋めて v-for でタスク一覧を表示せよ(4箇所)。

<li ___="task in tasks" :key="task.id">
  {{ task.___ }}
</li>

「追加」を押すと tasks.value.___(...) で配列に足し、行が増える。

ドリル 6: ヒント

ドリル名: 06-fill-vfor

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

  • 配列を繰り返すディレクティブは v-for
  • 末尾に足すメソッドは push(script 内なので tasks.value.push
  • 表示するのは task.title
  • :key="task.id" は一意な id を渡す(index は使わない)

ドリル 7: コンポーネント分割(props と emit)

ドリル名: 07-build-component

> 🛠️ 【タイプ: ゼロ実装問題】App.vue と子 components/TaskItem.vue の2ファイルに分割します。守るのは画面の機能(タイトルが並ぶ・削除で消える)。

問題

タスクリストを親子コンポーネントに分割せよ。

  • 親: タスク配列を持ち、v-for で子に :title を渡す
  • 子: 1件を表示し、「削除」ボタンで親に emit('remove')
  • 親: 通知を @remove で受けてその行を消す

初期データ: [{ id:1, title:'買い物' }, { id:2, title:'掃除' }]

ドリル 7: ヒント

ドリル名: 07-build-component

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

  • 子: defineProps({ title: String }) / defineEmits(['remove'])
  • 子の削除ボタン: @click="emit('remove')"
  • 親: import TaskItem from './components/TaskItem.vue'
  • 親で渡す: :title="task.title" / 受ける: @remove="removeTask(task.id)"
  • 削除: tasks.value = tasks.value.filter(t => t.id !== id)
  • データは親→子(props)、通知は子→親(emit)

考えてみよう

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

ch02-reactivity 演習ガイド

受講生向け演習問題集

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

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

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

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

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

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

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

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

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

3タイプ共通で守ること

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

このガイドのドリル一覧

#ドリル名タイトルタイプ難易度
101-fill-counterref + クリックハンドラ - カウンター実装🔧 穴埋め
202-fill-refref().value - リアクティブ変数の取り扱い🔧 穴埋め
303-add-stepステップ加算と偶奇表示 - カウンターに機能を足す➕ 機能追加
404-build-toggleON/OFF スイッチ - ref を使って白紙から組み立てる🛠️ ゼロ実装
505-add-vifv-if で状態メッセージを出し分ける➕ 機能追加★★
606-fill-vforv-for でタスク一覧を表示する🔧 穴埋め★★
707-build-componentコンポーネント分割 - props と emit でタスクリスト🛠️ ゼロ実装★★★

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

ドリル 1: ref + クリックハンドラ - カウンター実装

ドリル名: 01-fill-counter

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

問題

___ を埋めて、増やす・減らす・リセットができるカウンターを完成させよ。___ は全部で5箇所ある。

<script setup>
import { ___ } from 'vue'        <!-- ___1: ref をインポートする関数名 -->
const count = ___(0)             <!-- ___2: 初期値0のリアクティブ変数を作る -->

function reset() {
  count.___ = 0                  <!-- ___3: script 内で値を書き換えるときの構文 -->
}
</script>

<template>
  <p>カウント: {{ count }}</p>
  <button @___="count++">増やす</button>   <!-- ___4: クリックイベントの記号 -->
  <button @click="count--">減らす</button>
  <button @click="___">リセット</button>   <!-- ___5: 関数を呼ぶ書き方 -->
</template>

確認方法

  1. App.vue に貼り付けて npm run dev で起動する
  2. 「増やす」「減らす」「リセット」がすべて動くこと
  3. F12 コンソールにエラーが出ないこと

ドリル 1: ヒント

ドリル名: 01-fill-counter

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

  • ___1 / ___2: manuscript.md の「ref()」スライド参照
  • ___3: manuscript.md の「.value の使い分け」スライド参照(script 内では .value が必要)
  • ___4: クリックイベントは @click
  • ___5: 関数を渡すときは () を付けない(@click="reset"

ドリル 2: ref().value - リアクティブ変数の取り扱い

ドリル名: 02-fill-ref

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

問題

___ を埋めて、ボタンをクリックするとカウントが増えるアプリを完成させよ。___ は全部で3箇所ある。

<script setup>
import { ___ } from 'vue'        <!-- ___1: ref をインポートする関数名 -->
const count = ___(0)             <!-- ___2: 初期値0のリアクティブ変数を作る -->
</script>

<template>
  <p>カウント: {{ count }}</p>
  <button @click="___++">増やす</button>   <!-- ___3: テンプレートで変数を参照する名前 -->
</template>

確認方法

  1. App.vue に貼り付けて npm run dev で起動する
  2. 「増やす」ボタンをクリックするたびに数字が増えること

ドリル 2: ヒント

ドリル名: 02-fill-ref

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

  • ___1: Vue からリアクティブ変数を作る関数。import { ??? } from 'vue'
  • ___2: ref() に初期値を渡すと、リアクティブな変数が返る
  • ___3: テンプレートの中では .value を付けずに変数名だけで参照できる

ドリル 3: ステップ加算と偶奇表示 - カウンターに機能を足す

ドリル名: 03-add-step

> ➕ 【タイプ: 機能追加問題】 既存コードを残したまま、新しい機能を 追加 してください。実装方法は自由ですが、表示テキスト (カウント: 偶数 奇数) は守ること。

問題

「増やす/減らす」だけのカウンターに、2つの機能を 追加 せよ。

  1. 「+5」ボタン: クリックで count が 5 増える
  2. 偶奇の表示: count が偶数なら 偶数、奇数なら 奇数<p> で表示する
<template>
  <p>カウント: {{ count }}</p>
  <button @click="count++">増やす</button>
  <button @click="count--">減らす</button>
  <!-- 「+5」ボタンを追加 -->
  <!-- 偶数/奇数を表示する <p> を追加 -->
</template>

確認方法

  1. App.vue に貼り付けて npm run dev で起動する
  2. 「+5」で 5 ずつ増えること
  3. count が変わると「偶数/奇数」が切り替わること

ドリル 3: ヒント

ドリル名: 03-add-step

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

  • 「+5」は既存ボタンと同じ。@click="count += 5" でよい
  • 偶奇は count % 2 === 0 なら偶数
  • テンプレートで条件を出すには三項演算子: {{ 条件 ? 'A' : 'B' }}computed はまだ不要)

ドリル 4: ON/OFF スイッチ - ref を使って白紙から組み立てる

ドリル名: 04-build-toggle

> 🛠️ 【タイプ: ゼロ実装問題】 ほぼ空の App.vue から自力で全部書きます。<template> 構造・命名は自由。守るのは画面に出る文字だけ。

問題

次の仕様を満たすスイッチを 白紙から 作れ。

  1. 状態表示: ON なら 状態: ON、OFF なら 状態: OFF(初期は OFF
  2. 「切り替え」ボタン: 押すたびに ON ↔ OFF が反転する
  3. 押した回数表示: 押した回数: 0 から始まり、押すたびに 1 増える

守るのは画面の文字 (状態: ON / 状態: OFF / 押した回数: N) だけ。

確認方法

  1. npm run dev で起動する
  2. 初期表示が 状態: OFF / 押した回数: 0
  3. ボタンを押すと ONOFF が交互に変わり、回数が増えること

ドリル 4: ヒント

ドリル名: 04-build-toggle

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

  • 状態は真偽値の ref: const isOn = ref(false)、反転は isOn.value = !isOn.value
  • 回数も数値の ref: const taps = ref(0)、押すたびに taps.value++
  • 1つの関数の中で「反転」と「+1」を両方やってしまえばよい
  • 表示の出し分けは {{ isOn ? 'ON' : 'OFF' }}

ドリル 5: v-if で状態メッセージを出し分ける

ドリル名: 05-add-vif

> ➕ 【タイプ: 機能追加問題】 既存のカウンターを残したまま、状態メッセージを追加してください。表示テキスト (ゼロです マイナスです プラスです) は守ること。

問題

カウンターに、count の値に応じた状態メッセージを v-if / v-else-if / v-else で追加せよ。

条件表示
0ゼロです
マイナスマイナスです
プラスプラスです

3つのうち同時に1つだけ表示されること。

ドリル 5: ヒント

ドリル名: 05-add-vif

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

  • v-if="条件"v-else-if="条件"v-else の順に書く
  • 条件は式: count === 0 / count < 0
  • v-else には条件を書かない(「それ以外」)
  • template なので count.value は不要

ドリル 6: v-for でタスク一覧を表示する

ドリル名: 06-fill-vfor

> 🔧 【タイプ: 穴埋め問題】 ___ を埋めて、タスク配列を一覧表示し「追加」で増えるアプリを完成させてください。

問題

___ を埋めて v-for でタスク一覧を表示せよ(4箇所)。

<li ___="task in tasks" :key="task.id">
  {{ task.___ }}
</li>

「追加」を押すと tasks.value.___(...) で配列に足し、行が増える。

ドリル 6: ヒント

ドリル名: 06-fill-vfor

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

  • 配列を繰り返すディレクティブは v-for
  • 末尾に足すメソッドは push(script 内なので tasks.value.push
  • 表示するのは task.title
  • :key="task.id" は一意な id を渡す(index は使わない)

ドリル 7: コンポーネント分割(props と emit)

ドリル名: 07-build-component

> 🛠️ 【タイプ: ゼロ実装問題】App.vue と子 components/TaskItem.vue の2ファイルに分割します。守るのは画面の機能(タイトルが並ぶ・削除で消える)。

問題

タスクリストを親子コンポーネントに分割せよ。

  • 親: タスク配列を持ち、v-for で子に :title を渡す
  • 子: 1件を表示し、「削除」ボタンで親に emit('remove')
  • 親: 通知を @remove で受けてその行を消す

初期データ: [{ id:1, title:'買い物' }, { id:2, title:'掃除' }]

ドリル 7: ヒント

ドリル名: 07-build-component

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

  • 子: defineProps({ title: String }) / defineEmits(['remove'])
  • 子の削除ボタン: @click="emit('remove')"
  • 親: import TaskItem from './components/TaskItem.vue'
  • 親で渡す: :title="task.title" / 受ける: @remove="removeTask(task.id)"
  • 削除: tasks.value = tasks.value.filter(t => t.id !== id)
  • データは親→子(props)、通知は子→親(emit)

ch02-reactivity 演習解説

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

ドリル 1: カウンター実装 — 模範解答

ドリル名: 01-fill-counter

<script setup>
import { ref } from 'vue'
const count = ref(0)

function reset() {
  count.value = 0
}
</script>

<template>
  <p>カウント: {{ count }}</p>
  <button @click="count++">増やす</button>
  <button @click="count--">減らす</button>
  <button @click="reset">リセット</button>
</template>

ドリル 1: 解説

空欄解答なぜ
___1refimport { ref } from 'vue'。リアクティブな箱を作る関数
___2refref(0) で初期値 0 の箱を作る
___3valuescript 内では count.value = 0。箱の中身をさわる
___4clickクリックイベントは @click
___5reset関数は () なしで渡す(reset() は即実行になる)

ドリル 1: 観察ポイント

  • ___3count = 0 と書く.value の理解が抜けている。「script では箱の中身(.value)をさわる」が定着していないサイン
  • ___5reset() と書く → 動いてしまうが、ページ読み込み時に1回実行される。「関数を渡す vs 実行する」の区別を今ここで直す
  • @click="count++"(増やす)は通るのに reset でつまずく → 式と関数渡しの違いが原因。「式はその場で実行 / 関数は名前を渡す」と整理する

ドリル 2: ref().value — 模範解答

ドリル名: 02-fill-ref

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

<template>
  <p>カウント: {{ count }}</p>
  <button @click="count++">増やす</button>
</template>

ドリル 2: 解説

空欄解答なぜ
___1refVue から箱を作る関数を import
___2ref(0)初期値を渡してリアクティブな変数を作る
___3counttemplate 内なので .value は不要

> template 内では count.value ではなく count と書く。script 内で .value が要るのとは。ここが ref 最大のつまずき。

ドリル 2: 観察ポイント

  • template に {{ count.value }} と書く → 動いてしまうが冗長。「template は自動で中身を取り出す」を伝える
  • @click="count.value++" と書く → これも動くが不要。template 側は .value なしで統一させる
  • ドリル1とドリル2で script と template の .value の有無を対比させると定着する

ドリル 3: ステップ加算と偶奇表示 — 模範解答

ドリル名: 03-add-step

<template>
  <p>カウント: {{ count }}</p>
  <button @click="count++">増やす</button>
  <button @click="count--">減らす</button>
  <button @click="count += 5">+5</button>
  <p>{{ count % 2 === 0 ? '偶数' : '奇数' }}</p>
</template>

ドリル 3: 解説

  • @click="count += 5": クリックハンドラには「式」を直接書ける。count++ と同じ仲間。count = count + 5 でも可
  • {{ count % 2 === 0 ? '偶数' : '奇数' }}: template 内は「式」なので三項演算子が使える。count が変わると自動で再評価される(リアクティビティ)

> ここで computed を使わせない。「テンプレートの式だけで足りる」感覚を先に掴ませ、computed は必要になったとき(ch03 以降)に導入する。

ドリル 3: 観察ポイント

  • <script> に偶奇用の変数を作る → 間違いではないが、この段階では template の式で十分。過剰な状態を持たない感覚を養う
  • 偶奇表示が更新されないcount.value を template に書いていないか確認(template では .value 不要)
  • 「+5」を別の ref で実装しようとする → ステップ幅を変数にするのは将来の発展。今は固定値 += 5 でよい

ドリル 4: ON/OFF スイッチ — 模範解答

ドリル名: 04-build-toggle

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

const isOn = ref(false)
const taps = ref(0)

function toggle() {
  isOn.value = !isOn.value
  taps.value++
}
</script>

<template>
  <p>状態: {{ isOn ? 'ON' : 'OFF' }}</p>
  <button @click="toggle">切り替え</button>
  <p>押した回数: {{ taps }}</p>
</template>

ドリル 4: 解説

  • ref を 2 つ持つ: 状態(真偽値 isOn)と回数(数値 taps)は別の関心事。ref は何個作ってもよい
  • 1 つのハンドラで両方更新: toggle() の中で「反転」と「+1」をまとめる。クリック1回 = 状態変化1セット
  • {{ isOn ? 'ON' : 'OFF' }}: 真偽値を表示用の文字に変換するのも template の式で完結

> build タイプなので、class 名・関数名・template 構造は自由。テストが見るのは画面に出る文字状態: ON 等)だけ。

ドリル 4: 観察ポイント

  • .value を付け忘れるisOn = !isOn)→ script 内なのに .value が抜けている。ref の取り扱いが未定着のサイン
  • template に .value を書く{{ isOn.value }})→ template では自動 unwrap される
  • 回数を別ボタンにしてしまう → 仕様は「切り替えを押すたびに回数が増える」。1ハンドラに集約できているかが評価点
  • 状態を文字列 ref(ref('OFF'))で持つ → 動くが、真偽値で持って表示時に変換する方が素直。どちらでも仕様は満たすので減点はしない

ドリル 5: v-if で状態メッセージ — 模範解答

ドリル名: 05-add-vif

<template>
  <p>カウント: {{ count }}</p>
  <button @click="count++">増やす</button>
  <button @click="count--">減らす</button>
  <p v-if="count === 0">ゼロです</p>
  <p v-else-if="count < 0">マイナスです</p>
  <p v-else>プラスです</p>
</template>

ドリル 5: 解説と観察ポイント

  • v-if / v-else-if / v-else は if/else if/else と同じ。1つだけが表示される
  • v-else には条件を書かない

観察ポイント:

  • 3つを別々の v-if で書く → 動くが排他が不明瞭。v-else-if/v-else でつなぐ
  • v-else を離れた場所に書く → v-if の直後の要素でないと効かない
  • 0 の扱い → 0 を独立させる仕様なので === 0 を先に判定する

ドリル 6: v-for でタスク一覧 — 模範解答

ドリル名: 06-fill-vfor

空欄解答
___1ref
___2push
___3v-for
___4title
<li v-for="task in tasks" :key="task.id">
  {{ task.title }}
</li>

ドリル 6: 解説と観察ポイント

  • tasks.value.push(...) で配列に足すと v-for が自動で1行増やす
  • :key="task.id" で各行を区別する

観察ポイント:

  • tasks.push(.value 漏れ) → script 内は tasks.value.push
  • {{ task }}[object Object] になる。task.title を指定
  • :key を index に → id があるので id を使う

ドリル 7: コンポーネント分割 — 模範解答

ドリル名: 07-build-component

components/TaskItem.vue:

<script setup>
const props = defineProps({ title: { type: String, required: true } })
const emit = defineEmits(['remove'])
</script>

<template>
  <li>{{ props.title }} <button @click="emit('remove')">削除</button></li>
</template>

ドリル 7: 親(App.vue)

<script setup>
import { ref } from 'vue'
import TaskItem from './components/TaskItem.vue'

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

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

<template>
  <ul>
    <TaskItem
      v-for="task in tasks"
      :key="task.id"
      :title="task.title"
      @remove="removeTask(task.id)"
    />
  </ul>
</template>

ドリル 7: 解説と観察ポイント

  • props は親→子:title で渡し defineProps で受ける)
  • emit は子→親(子が emit('remove')、親が @remove で受ける)
  • 削除は filter で「その id 以外」を残す

観察ポイント:

  • 子から直接 tasks を消そうとする → 子は親の配列を知らない。emit で頼むのが正しい設計
  • id を渡さない@remove="removeTask")→ どのタスクか不明。removeTask(task.id) で渡す
  • props を直接書き換える → props は読み取り専用。Vue が警告
  • :key を忘れる → 削除後の再描画で行がズレる

Q: カウンター(増減・リセット)の空欄を埋める

問題

___ を埋めて、増やす・減らす・リセットができるカウンターを完成させよ。___ は全部で5箇所ある。

<script setup>
import { ___ } from 'vue'        <!-- ___1: ref をインポートする関数名 -->
const count = ___(0)             <!-- ___2: 初期値0のリアクティブ変数を作る -->

function reset() {
  count.___ = 0                  <!-- ___3: script 内で値を書き換えるときの構文 -->
}
</script>

<template>
  <p>カウント: {{ count }}</p>
  <button @___="count++">増やす</button>   <!-- ___4: クリックイベントの記号 -->
  <button @click="count--">減らす</button>
  <button @click="___">リセット</button>   <!-- ___5: 関数を呼ぶ書き方 -->
</template>

確認方法

  1. App.vue に貼り付けて npm run dev で起動する
  2. 「増やす」「減らす」「リセット」がすべて動くこと
  3. F12 コンソールにエラーが出ないこと

ヒント

  • ___1 / ___2: manuscript.md の「ref()」スライド参照
  • ___3: manuscript.md の「.value の使い分け」スライド参照(script 内では .value が必要)
  • ___4: クリックイベントは @click
  • ___5: 関数を渡すときは () を付けない(@click="reset"

Q: ref とテンプレートの空欄を埋める

問題

___ を埋めて、ボタンをクリックするとカウントが増えるアプリを完成させよ。___ は全部で3箇所ある。

<script setup>
import { ___ } from 'vue'        <!-- ___1: ref をインポートする関数名 -->
const count = ___(0)             <!-- ___2: 初期値0のリアクティブ変数を作る -->
</script>

<template>
  <p>カウント: {{ count }}</p>
  <button @click="___++">増やす</button>   <!-- ___3: テンプレートで変数を参照する名前 -->
</template>

確認方法

  1. App.vue に貼り付けて npm run dev で起動する
  2. 「増やす」ボタンをクリックするたびに数字が増えること

ヒント: ref とテンプレートの空欄

  • ___1: Vue からリアクティブ変数を作る関数。import { ??? } from 'vue'
  • ___2: ref() に初期値を渡すと、リアクティブな変数が返る
  • ___3: テンプレートの中では .value を付けずに変数名だけで参照できる

解答: ref とテンプレートの空欄

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

<template>
  <p>カウント: {{ count }}</p>
  <button @click="count++">増やす</button>
</template>
  • ___1ref
  • ___2ref(0)
  • ___3count

> テンプレート内では count.value ではなく count と書く。script setup 内で .value が必要なのとは違う。

Q: カウンターに「+5」と「偶奇表示」を追加する

問題

下のカウンターは「増やす/減らす」だけができる。ここに 2つの機能を追加 せよ。

  1. 「+5」ボタン: クリックすると count が 5 増える
  2. 偶奇の表示: count が偶数なら 偶数、奇数なら 奇数 と画面に出す(<p> で表示する)
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>

<template>
  <p>カウント: {{ count }}</p>
  <button @click="count++">増やす</button>
  <button @click="count--">減らす</button>
  <!-- ここに「+5」ボタンを追加する -->
  <!-- ここに偶数/奇数を表示する <p> を追加する -->
</template>

ルール

  • 既存の「増やす/減らす」は壊さない
  • 表示テキストは カウント: 偶数 奇数 をそのまま使う(テストが見ている)
  • 実装方法は自由(インラインの @click="count += 5" でも、関数を作ってもよい)

確認方法

  1. App.vue に貼り付けて npm run dev で起動する
  2. 「+5」で 5 ずつ増えること
  3. count が変わると「偶数/奇数」の表示が切り替わること
  4. F12 コンソールにエラーが出ないこと

ヒント

  • 「+5」ボタン: 既存のボタンと同じ書き方。@click="count += 5" のようにインラインで書ける
  • 偶奇の判定: count を 2 で割った余りで決まる(count % 2 === 0 なら偶数)
  • テンプレートで条件を出す: {{ 条件 ? 'A' : 'B' }} の三項演算子が使える(<script> を増やさなくてよい)
  • まだ computed は習っていない。テンプレート内の式だけで十分に書ける

Q: ON/OFF スイッチを白紙から作る

問題

App.vueほぼ白紙から 書いて、次の仕様を満たすスイッチを作れ。

仕様

  1. 状態を表示する <p>: 現在 ON なら 状態: ON、OFF なら 状態: OFF と出す(初期は OFF
  2. 「切り替え」ボタン: クリックするたびに ON ↔ OFF が反転する
  3. 押した回数の表示 <p>: 押した回数: 0 から始まり、「切り替え」を押すたびに 1 ずつ増える

ルール(build タイプ)

  • <template> の構造・class・関数名・変数名は 完全に自由
  • 守るのは 画面に出る文字 だけ: 状態: ON / 状態: OFF / 押した回数: N
  • ファイル名 App.vue は変えない
  • tests/ は触らない

使う道具(ch02 までで習ったもの)

  • ref(初期値) でリアクティブな変数を作る
  • <script> 内では .value で読み書き
  • @click="関数名" でクリックを受ける

確認方法

  1. npm run dev で起動する
  2. 初期表示が 状態: OFF / 押した回数: 0
  3. ボタンを押すと ONOFF が交互に変わり、回数が増える
  4. F12 コンソールにエラーが出ないこと

ヒント

  • 状態は 真偽値の ref で持つと楽: const isOn = ref(false)
  • 反転は isOn.value = !isOn.value<script> 内なので .value が必要)
  • 回数も 数値の ref: const taps = ref(0)、押すたびに taps.value++
  • 1つの関数の中で「反転」と「+1」を両方やってしまえばよい
  • 表示の出し分けは三項演算子: {{ isOn ? 'ON' : 'OFF' }}
  • ref は何個作ってもよい

Q: カウントの状態を v-if で出し分ける

問題

このカウンターに、count の値に応じた状態メッセージを追加せよ。v-if / v-else-if / v-else を使う。

条件表示するテキスト
count が 0ゼロです
count がマイナスマイナスです
それ以外(プラス)プラスです
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>

<template>
  <p>カウント: {{ count }}</p>
  <button @click="count++">増やす</button>
  <button @click="count--">減らす</button>
  <!-- ここに状態メッセージ(v-if / v-else-if / v-else)を追加する -->
</template>

ルール

  • 既存の「増やす/減らす」は壊さない
  • 表示テキストは ゼロです マイナスです プラスです をそのまま使う(テストが見ている)
  • 3つのメッセージは同時に1つだけ表示されること(v-if 系で出し分ける)

確認方法

  1. App.vue に貼り付けて npm run dev で起動する
  2. 初期表示で ゼロです が出る
  3. 「増やす」で プラスです、0 より下げて マイナスです に変わる
  4. F12 コンソールにエラーが出ないこと

ヒント

  • v-if="条件" を付けた要素は、条件が true のときだけ表示される
  • 3段階の出し分けは v-ifv-else-ifv-else の順に書く
  • 条件は JavaScript の式: count === 0 / count < 0
  • v-else には条件を書かない(「それ以外」の意味)
  • template の中なので count.value は不要

Q: v-for でタスク一覧を表示する空欄を埋める

問題

___ を埋めて、タスクの配列を一覧表示し、「追加」で行が増えるアプリを完成させよ。___ は全部で4箇所ある。

<script setup>
import { ___ } from 'vue'                      // ___1: リアクティブな箱を作る関数
const tasks = ref([
  { id: 1, title: '買い物' },
  { id: 2, title: '掃除' },
])
let nextId = 3

function addTask() {
  tasks.value.___({ id: nextId++, title: `タスク${nextId}` })  // ___2: 配列の末尾に足すメソッド
}
</script>

<template>
  <button @click="addTask">追加</button>
  <ul>
    <li ___="task in tasks" :key="task.id">    <!-- ___3: 配列を繰り返すディレクティブ -->
      {{ task.___ }}                            <!-- ___4: タスクのタイトル -->
    </li>
  </ul>
</template>

確認方法

  1. App.vue に貼り付けて npm run dev で起動する
  2. 最初から「買い物」「掃除」が表示される
  3. 「追加」を押すとタスクが1つずつ増える
  4. F12 コンソールにエラーが出ないこと

ヒント

  • ___1: Vue からリアクティブな箱を作る関数。import { ??? } from 'vue'
  • ___2: 配列の末尾に要素を足す JavaScript のメソッド(push
  • ___3: 配列を繰り返して要素を並べるディレクティブ(v-for
  • ___4: 取り出した task のプロパティ名。{ id, title } のどちらを表示する?
  • :key="task.id" は既に書いてある。一意な id を key にするのが鉄則

Q: コンポーネントに分割してタスクリストを作る

問題

タスク一覧アプリを、親(App.vue)と子(TaskItem.vue)に分割して作れ。

仕様

  1. App.vue がタスク配列を持ち、v-for で子に渡す
  2. TaskItem.vue は1件のタスクを表示する(propstitle を受け取る)
  3. 各タスクに「削除」ボタンがあり、押すと親に通知(emit)してその行を消す

初期データ

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

ルール(build タイプ)

  • ファイルは2つ:App.vue(親)と components/TaskItem.vue(子)
  • props は親→子、emit は子→親の向きを守る
  • 守るのは画面の機能:タスクのタイトルが並ぶ/削除ボタンで消える
  • 表示テキスト:各タスクの title、ボタンは 削除

確認方法

  1. npm run dev で起動する
  2. 「買い物」「掃除」が並ぶ
  3. ある行の「削除」を押すと、その行だけ消える
  4. F12 コンソールにエラーが出ないこと

ヒント

子コンポーネント(components/TaskItem.vue)

<script setup>
const props = defineProps({ title: String })
const emit = defineEmits(['remove'])
</script>

<template>
  <li>
    {{ props.title }}
    <button @click="emit('remove')">削除</button>
  </li>
</template>

親(App.vue)のポイント

  • import TaskItem from './components/TaskItem.vue' で子を読み込む
  • v-for で TaskItem を並べ、:title="task.title" で props を渡す
  • @remove="removeTask(task.id)" で子の通知を受ける
  • 削除は tasks.value = tasks.value.filter(t => t.id !== id)

向きの整理

  • props(親→子): :title="..." で渡す
  • emit(子→親): 子が emit('remove')、親が @remove で受ける