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 を使って直す
let を ref に変える
さっきのコードを 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 = 0 を const count = ref(0) にしただけ(と .value)。
3つだけ変えた
| 変更前 | 変更後 |
|---|---|
| (import なし) | import { ref } from 'vue' |
let count = 0 | const count = ref(0) |
count = count + 1 | count.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-if | v-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-for と v-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
http://localhost:5173を開く- 「増やす」「減らす」「リセット」を押す
- 数字が変わることを確認する
- F12 でコンソールにエラーが出ていないか見る
11. つまずいたら
画面が変わらないとき
順番に確認しよう。
refを使っているか?(letではなくconst ... = ref(...))- script で
.valueを付けているか?(count.value++) - import を書いたか?(
import { ref } from 'vue')
> この3つのどれかが抜けていることがほとんど。
エラーが出たとき
F12 を押してコンソールを見る。
*(図は省略)*
> エラーは「どこが悪いか」を教えてくれる。読めば直せる。
よくあるエラー
| エラー / 症状 | 原因 | 直し方 |
|---|---|---|
ref is not defined | import 忘れ | import { ref } from 'vue' |
| 画面が変わらない | let を使っている | ref にする |
| 画面が変わらない | .value 漏れ | script で .value を付ける |
| ボタンが即実行される | reset() と書いた | () を外す |
12. 用語集とまとめ
今日の用語(1/2)
| 用語 | 意味 |
|---|---|
| リアクティブ | 値が変わると画面が自動で追従すること |
| ref | 値を「Vue が見張れる箱」にする関数 |
| .value | ref の箱の中身。script でさわるときに付ける |
| {{ }} | template で変数の中身を表示する書き方 |
| @click | クリックされたときの処理を指定するイベント |
今日の用語(2/2)
| 用語 | 意味 |
|---|---|
| v-if / v-else | 条件によって表示を出し分ける |
| v-show | CSS で表示/非表示を切り替える(DOM は残る) |
| v-for | 配列を繰り返して要素を並べる |
| :key | v-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/フォルダは触らない (採点ロジック)- テンプレートの 表示テキスト (「カウント:」等) はテストが見ているので残す
このガイドのドリル一覧
| # | ドリル名 | タイトル | タイプ | 難易度 |
|---|---|---|---|---|
| 1 | 01-fill-counter | ref + クリックハンドラ - カウンター実装 | 🔧 穴埋め | ★ |
| 2 | 02-fill-ref | ref().value - リアクティブ変数の取り扱い | 🔧 穴埋め | ★ |
| 3 | 03-add-step | ステップ加算と偶奇表示 - カウンターに機能を足す | ➕ 機能追加 | ★ |
| 4 | 04-build-toggle | ON/OFF スイッチ - ref を使って白紙から組み立てる | 🛠️ ゼロ実装 | ★ |
| 5 | 05-add-vif | v-if で状態メッセージを出し分ける | ➕ 機能追加 | ★★ |
| 6 | 06-fill-vfor | v-for でタスク一覧を表示する | 🔧 穴埋め | ★★ |
| 7 | 07-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>確認方法
App.vueに貼り付けてnpm run devで起動する- 「増やす」「減らす」「リセット」がすべて動くこと
- 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>確認方法
App.vueに貼り付けてnpm run devで起動する- 「増やす」ボタンをクリックするたびに数字が増えること
ドリル 2: ヒント
ドリル名: 02-fill-ref
> ⚠️ 自力で考えてから読んでください
___1: Vue からリアクティブ変数を作る関数。import { ??? } from 'vue'___2:ref()に初期値を渡すと、リアクティブな変数が返る___3: テンプレートの中では.valueを付けずに変数名だけで参照できる
ドリル 3: ステップ加算と偶奇表示 - カウンターに機能を足す
ドリル名: 03-add-step
> ➕ 【タイプ: 機能追加問題】 既存コードを残したまま、新しい機能を 追加 してください。実装方法は自由ですが、表示テキスト (カウント: 偶数 奇数) は守ること。
問題
「増やす/減らす」だけのカウンターに、2つの機能を 追加 せよ。
- 「+5」ボタン: クリックで
countが 5 増える - 偶奇の表示:
countが偶数なら偶数、奇数なら奇数と<p>で表示する
<template>
<p>カウント: {{ count }}</p>
<button @click="count++">増やす</button>
<button @click="count--">減らす</button>
<!-- 「+5」ボタンを追加 -->
<!-- 偶数/奇数を表示する <p> を追加 -->
</template>確認方法
App.vueに貼り付けてnpm run devで起動する- 「+5」で 5 ずつ増えること
- 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> 構造・命名は自由。守るのは画面に出る文字だけ。
問題
次の仕様を満たすスイッチを 白紙から 作れ。
- 状態表示: ON なら
状態: ON、OFF なら状態: OFF(初期は OFF) - 「切り替え」ボタン: 押すたびに ON ↔ OFF が反転する
- 押した回数表示:
押した回数: 0から始まり、押すたびに 1 増える
守るのは画面の文字 (状態: ON / 状態: OFF / 押した回数: N) だけ。
確認方法
npm run devで起動する- 初期表示が
状態: OFF/押した回数: 0 - ボタンを押すと
ON↔OFFが交互に変わり、回数が増えること
ドリル 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/フォルダは触らない (採点ロジック)- テンプレートの 表示テキスト (「カウント:」等) はテストが見ているので残す
このガイドのドリル一覧
| # | ドリル名 | タイトル | タイプ | 難易度 |
|---|---|---|---|---|
| 1 | 01-fill-counter | ref + クリックハンドラ - カウンター実装 | 🔧 穴埋め | ★ |
| 2 | 02-fill-ref | ref().value - リアクティブ変数の取り扱い | 🔧 穴埋め | ★ |
| 3 | 03-add-step | ステップ加算と偶奇表示 - カウンターに機能を足す | ➕ 機能追加 | ★ |
| 4 | 04-build-toggle | ON/OFF スイッチ - ref を使って白紙から組み立てる | 🛠️ ゼロ実装 | ★ |
| 5 | 05-add-vif | v-if で状態メッセージを出し分ける | ➕ 機能追加 | ★★ |
| 6 | 06-fill-vfor | v-for でタスク一覧を表示する | 🔧 穴埋め | ★★ |
| 7 | 07-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>確認方法
App.vueに貼り付けてnpm run devで起動する- 「増やす」「減らす」「リセット」がすべて動くこと
- 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>確認方法
App.vueに貼り付けてnpm run devで起動する- 「増やす」ボタンをクリックするたびに数字が増えること
ドリル 2: ヒント
ドリル名: 02-fill-ref
> ⚠️ 自力で考えてから読んでください
___1: Vue からリアクティブ変数を作る関数。import { ??? } from 'vue'___2:ref()に初期値を渡すと、リアクティブな変数が返る___3: テンプレートの中では.valueを付けずに変数名だけで参照できる
ドリル 3: ステップ加算と偶奇表示 - カウンターに機能を足す
ドリル名: 03-add-step
> ➕ 【タイプ: 機能追加問題】 既存コードを残したまま、新しい機能を 追加 してください。実装方法は自由ですが、表示テキスト (カウント: 偶数 奇数) は守ること。
問題
「増やす/減らす」だけのカウンターに、2つの機能を 追加 せよ。
- 「+5」ボタン: クリックで
countが 5 増える - 偶奇の表示:
countが偶数なら偶数、奇数なら奇数と<p>で表示する
<template>
<p>カウント: {{ count }}</p>
<button @click="count++">増やす</button>
<button @click="count--">減らす</button>
<!-- 「+5」ボタンを追加 -->
<!-- 偶数/奇数を表示する <p> を追加 -->
</template>確認方法
App.vueに貼り付けてnpm run devで起動する- 「+5」で 5 ずつ増えること
- 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> 構造・命名は自由。守るのは画面に出る文字だけ。
問題
次の仕様を満たすスイッチを 白紙から 作れ。
- 状態表示: ON なら
状態: ON、OFF なら状態: OFF(初期は OFF) - 「切り替え」ボタン: 押すたびに ON ↔ OFF が反転する
- 押した回数表示:
押した回数: 0から始まり、押すたびに 1 増える
守るのは画面の文字 (状態: ON / 状態: OFF / 押した回数: N) だけ。
確認方法
npm run devで起動する- 初期表示が
状態: OFF/押した回数: 0 - ボタンを押すと
ON↔OFFが交互に変わり、回数が増えること
ドリル 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: 解説
| 空欄 | 解答 | なぜ |
|---|---|---|
___1 | ref | import { ref } from 'vue'。リアクティブな箱を作る関数 |
___2 | ref | ref(0) で初期値 0 の箱を作る |
___3 | value | script 内では count.value = 0。箱の中身をさわる |
___4 | click | クリックイベントは @click |
___5 | reset | 関数は () なしで渡す(reset() は即実行になる) |
ドリル 1: 観察ポイント
___3でcount = 0と書く →.valueの理解が抜けている。「script では箱の中身(.value)をさわる」が定着していないサイン___5でreset()と書く → 動いてしまうが、ページ読み込み時に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: 解説
| 空欄 | 解答 | なぜ |
|---|---|---|
___1 | ref | Vue から箱を作る関数を import |
___2 | ref(0) | 初期値を渡してリアクティブな変数を作る |
___3 | count | template 内なので .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
| 空欄 | 解答 |
|---|---|
___1 | ref |
___2 | push |
___3 | v-for |
___4 | title |
<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>確認方法
App.vueに貼り付けてnpm run devで起動する- 「増やす」「減らす」「リセット」がすべて動くこと
- 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>確認方法
App.vueに貼り付けてnpm run devで起動する- 「増やす」ボタンをクリックするたびに数字が増えること
ヒント: 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>___1→ref___2→ref(0)___3→count
> テンプレート内では count.value ではなく count と書く。script setup 内で .value が必要なのとは違う。
Q: カウンターに「+5」と「偶奇表示」を追加する
問題
下のカウンターは「増やす/減らす」だけができる。ここに 2つの機能を追加 せよ。
- 「+5」ボタン: クリックすると
countが 5 増える - 偶奇の表示:
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"でも、関数を作ってもよい)
確認方法
App.vueに貼り付けてnpm run devで起動する- 「+5」で 5 ずつ増えること
- count が変わると「偶数/奇数」の表示が切り替わること
- F12 コンソールにエラーが出ないこと
ヒント
- 「+5」ボタン: 既存のボタンと同じ書き方。
@click="count += 5"のようにインラインで書ける - 偶奇の判定:
countを 2 で割った余りで決まる(count % 2 === 0なら偶数) - テンプレートで条件を出す:
{{ 条件 ? 'A' : 'B' }}の三項演算子が使える(<script>を増やさなくてよい) - まだ
computedは習っていない。テンプレート内の式だけで十分に書ける
Q: ON/OFF スイッチを白紙から作る
問題
App.vue を ほぼ白紙から 書いて、次の仕様を満たすスイッチを作れ。
仕様
- 状態を表示する
<p>: 現在 ON なら状態: ON、OFF なら状態: OFFと出す(初期は OFF) - 「切り替え」ボタン: クリックするたびに ON ↔ OFF が反転する
- 押した回数の表示
<p>:押した回数: 0から始まり、「切り替え」を押すたびに 1 ずつ増える
ルール(build タイプ)
<template>の構造・class・関数名・変数名は 完全に自由- 守るのは 画面に出る文字 だけ:
状態: ON/状態: OFF/押した回数: N - ファイル名
App.vueは変えない tests/は触らない
使う道具(ch02 までで習ったもの)
ref(初期値)でリアクティブな変数を作る<script>内では.valueで読み書き@click="関数名"でクリックを受ける
確認方法
npm run devで起動する- 初期表示が
状態: OFF/押した回数: 0 - ボタンを押すと
ON↔OFFが交互に変わり、回数が増える - 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 系で出し分ける)
確認方法
App.vueに貼り付けてnpm run devで起動する- 初期表示で
ゼロですが出る - 「増やす」で
プラスです、0 より下げてマイナスですに変わる - F12 コンソールにエラーが出ないこと
ヒント
v-if="条件"を付けた要素は、条件が true のときだけ表示される- 3段階の出し分けは
v-if→v-else-if→v-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>確認方法
App.vueに貼り付けてnpm run devで起動する- 最初から「買い物」「掃除」が表示される
- 「追加」を押すとタスクが1つずつ増える
- 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)に分割して作れ。
仕様
- 親
App.vueがタスク配列を持ち、v-forで子に渡す - 子
TaskItem.vueは1件のタスクを表示する(propsでtitleを受け取る) - 各タスクに「削除」ボタンがあり、押すと親に通知(
emit)してその行を消す
初期データ
const tasks = ref([
{ id: 1, title: '買い物' },
{ id: 2, title: '掃除' },
])ルール(build タイプ)
- ファイルは2つ:
App.vue(親)とcomponents/TaskItem.vue(子) - props は親→子、emit は子→親の向きを守る
- 守るのは画面の機能:タスクのタイトルが並ぶ/削除ボタンで消える
- 表示テキスト:各タスクの
title、ボタンは削除
確認方法
npm run devで起動する- 「買い物」「掃除」が並ぶ
- ある行の「削除」を押すと、その行だけ消える
- 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で受ける