Vue 3 で「クリックしたら値が変わる」「フォームに入力した値が画面に反映される」あたりを触ってみたので、その学習メモです。Vue では、ただ JavaScript の変数を書き換えるだけではなく、Vue が追跡できる形で状態を持つ必要があり、ref() や reactive() がその中心になるようです。 ja.vuejs
画像解析の結果をブラウザ側で見せる場面を少しずつイメージしながら、フロントの基本を追っています。まだ学習途中なので、今の理解で見えている範囲を整理するつもりで書いています。
| 「“値が変わる”と“画面が更新される”は、最初は同じに見えるけど、Vue ではそこが分かれ目なんだよね」 |
今日やったこと
今日は「値の表示」「クリックイベント」「ref と reactive の違い」を、フォーム画面をイメージしながら確認しました。Vue のテンプレートでは {{ }} を使って値を表示でき、簡単な式も書けます。 ja.vuejs
まずは、template の中で値を表示する基本を確認しました。
<script setup>
// script setup の中には JavaScript を書ける
const userName = 'Yuki'
let userAge = 30
</script>
<template>
<!-- 変数をそのまま表示する -->
<h1>Hello, {{ userName }}!</h1>
<!-- 簡単な式も書ける -->
<p>Age: {{ userAge - 1 }}</p>
</template>
この段階では、「Vue の画面の中に JavaScript の値を差し込める」という感覚をまず確認した、という感じでした。{{ }} の中に式が書けるのは便利ですが、複雑なことまで template 側に寄せすぎると読みづらくなりそうなので、そこは少し意識しておきたいところです。
| 「まずは“見える”の確認だね。動かす前に、値が画面に出るだけでもかなり前進」 |
普通の変数では更新されない
次に、クリックしたときのイベント処理を試しました。Vue では @click でイベントハンドラを呼べますが、普通の変数を書き換えただけでは、Vue がその変化を追跡できず、表示は更新されません。 ja.vuejs
最初は、こんなコードを試しました。
<script setup>
const userName = 'Yuki'
let userAge = 30
function increaseAge() {
// ボタンを押すたびに値自体は増える
console.log('現在の年齢:', userAge)
userAge++
}
</script>
<template>
<h1>Hello, {{ userName }}!</h1>
<p>Age: {{ userAge }}</p>
<button @click="increaseAge">年齢を増やします</button>
</template>
ただ、これは console 上では値が増えていても、ブラウザ表示は更新されませんでした。今の理解では、ここで起きていたのは「JavaScript の変数は変わったけれど、Vue から見ると“追跡対象の状態”ではなかった」ということです。 ja.vuejs
| 「`console` では増えてるのに画面はそのまま、ここで一回 “あれ?” ってなるんだよね」 |
ゲームっぽく考えると、普通の変数は「ゲーム内部のメモ帳に数字を書き換えただけ」の状態に近いです。裏では数値が変わっていても、その変更が UI 側に自動で伝わる仕組みにはなっていない、というイメージです。
ref は小さな状態に向いている
そこで ref() を使って、Vue が変更を追いかけられる形にしました。Vue のリアクティビティの基礎では、ref() は単一の値をリアクティブに扱うための仕組みとして説明されています。 ja.vuejs
<script setup>
import { ref } from 'vue'
const userName = 'Yuki'
// ref で包むと、Vue が変更を追跡できるようになる
const userAge = ref(30)
function increaseAge() {
// script 側では .value を使って値を更新する
userAge.value++
}
</script>
<template>
<h1>Hello, {{ userName }}!</h1>
<!-- template 側では .value を書かなくてよい -->
<p>Age: {{ userAge }}</p>
<button @click="increaseAge">年齢を増やします</button>
</template>
この書き方にすると、クリックしたときに画面の表示も更新されるようになりました。ref はリアクティブな参照を返し、script 側では .value で中身に触り、template 側では自動でアンラップされるので .value は不要です。 vueframework
ここはかなり混乱しやすいところでしたが、「script は仕組みが見える場所、template は少しやさしく扱える場所」と考えると、自分の中では少し整理しやすくなりました。
「ref は“1本のゲージ”って思うとわかりやすいよ。中身をいじるときだけ .value がいる感じ」
| 「“Nodeまわり”ってひとまとめにしないで、土台・管理・開発用ツールってざっくり分けると、だいぶ視界が晴れるよ」 |
たとえば、HP バーのようなイメージだとこんな感じです。
<script setup>
import { ref } from 'vue'
// HPバーを1本用意するイメージ
const hp = ref(100)
function takeDamage() {
// バトル中にHPを減らす
hp.value -= 10
}
</script>
<template>
<p>HP: {{ hp }}</p>
<button @click="takeDamage">ダメージを受ける</button>
</template>
この場合、script 側はゲームロジックのように中身を直接変更するので .value が必要です。一方で template 側は、そのゲージを受け取って表示するだけなので、{{ hp }} のように書けます。 vueframework
reactive はまとまりのあるデータ向き
次にややこしかったのが reactive() でした。Vue のリアクティビティでは、reactive() はオブジェクトをリアクティブにしたいときに使う仕組みとして説明されています。 ja.vuejs
<script setup>
import { reactive } from 'vue'
// ステータス画面全体をまとめて持つイメージ
const status = reactive({
name: 'Yuki',
level: 5,
hp: 100,
mp: 40,
})
function levelUp() {
// オブジェクトの各プロパティを直接更新できる
status.level++
status.hp += 20
status.mp += 10
}
</script>
<template>
<p>Name: {{ status.name }}</p>
<p>Level: {{ status.level }}</p>
<p>HP: {{ status.hp }}</p>
<p>MP: {{ status.mp }}</p>
<button @click="levelUp">レベルアップ</button>
</template>
これは、自分の中では「1本のゲージ」ではなく、「キャラクターのステータス画面全体」をそのまま扱うイメージに近いです。名前・レベル・HP・MP のように、関連する値をひとまとまりで持ちたいときは reactive のほうが自然に感じました。 ja.vuejs
| 「`reactive` は“1枚のステータス画面”って覚えると、`ref` との違いがかなり見えやすいね」」 |
フォームは reactive がしっくりくる
フォーム入力では v-model が入力値と状態を同期してくれます。Vue のフォーム入力バインディングでは、v-model を使って入力欄と状態を双方向に結びつける形が基本になります。 vuejs
<script setup>
import { reactive } from 'vue'
// キャラメイク画面の入力シート全体
const form = reactive({
playerName: '',
age: 18,
job: 'wizard',
profile: '',
})
function submitForm() {
// 入力された内容を確認する
console.log(form)
}
</script>
<template>
<form @submit.prevent="submitForm">
<label>
Player Name
<input v-model="form.playerName" type="text" />
</label>
<label>
Age
<input v-model="form.age" type="number" />
</label>
<label>
Job
<select v-model="form.job">
<option value="wizard">Wizard</option>
<option value="warrior">Warrior</option>
<option value="archer">Archer</option>
</select>
</label>
<label>
Profile
<textarea v-model="form.profile"></textarea>
</label>
<button type="submit">作成する</button>
</form>
</template>
この例だと、名前欄だけ、年齢欄だけを別々に持つより、「キャラメイクシート全体をまとめて管理する」イメージのほうが自然でした。フォームでは項目が複数あることが多いので、こういうときは reactive のほうが見通しがよさそうだと感じています。 ja.vuejs
| 「フォームは“入力欄の集合”というより、“1枚の入力シート”って見たほうが整理しやすいよね」 |
補助UIは ref が合いやすい
フォームを触っていると、入力値そのものとは別に、細かい UI 状態も出てきます。たとえば「送信中かどうか」「エラーメッセージを出すか」「パスワードを表示するか」のような状態です。
<script setup>
import { reactive, ref } from 'vue'
const form = reactive({
email: '',
password: '',
})
// 補助的なUI状態は、1つずつ ref で持つ
const isSubmitting = ref(false)
const showPassword = ref(false)
const errorMessage = ref('')
async function submitForm() {
isSubmitting.value = true
errorMessage.value = ''
try {
console.log(form)
} catch (error) {
errorMessage.value = '送信に失敗しました'
} finally {
isSubmitting.value = false
}
}
</script>
<template>
<form @submit.prevent="submitForm">
<label>
Email
<input v-model="form.email" type="email" />
</label>
<label>
Password
<input
v-model="form.password"
:type="showPassword ? 'text' : 'password'"
/>
</label>
<button type="button" @click="showPassword = !showPassword">
表示切り替え
</button>
<p v-if="errorMessage">{{ errorMessage }}</p>
<button type="submit" :disabled="isSubmitting">
{{ isSubmitting ? '送信中...' : '送信する' }}
</button>
</form>
</template>
ゲームでいうと、form は「キャラメイクシート本体」です。一方で isSubmitting や showPassword は、「今セーブ中です」「詳細表示を切り替える」といった補助 UI のスイッチに近いです。
このあたりは、1つ1つが独立した小さな状態なので、ref のほうが合っていそうだと感じました。つまり、主役のデータは reactive、補助 UI は ref と分けると、かなり整理しやすいです。 ja.vuejs
| 「“入力内容そのもの”と“画面のちょい設定”を分けて考えると、一気に見通しよくなるね」 |
混ざると急に難しい
一番ややこしかったのは、ref と reactive が混ざったときです。Vue では、reactive オブジェクトのプロパティとして ref を持つと、その ref は自動でアンラップされます。 vueframework
まず、reactive の中に ref が入っているパターンです。
<script setup>
import { ref, reactive } from 'vue'
const state = reactive({
level: ref(5),
name: 'Yuki',
})
function levelUp() {
// reactive の中にある ref は、自動で中身を扱ってくれる
state.level++
}
</script>
<template>
<p>Name: {{ state.name }}</p>
<p>Level: {{ state.level }}</p>
<button @click="levelUp">レベルアップ</button>
</template>
このパターンでは、state.level に対して .value を書かなくても動きます。reactive オブジェクトの中の ref は、普通のプロパティのように扱えるからです。 vueframework
一方で、ref の中にオブジェクトを入れた場合は、外側が ref の箱なので、script 側では .value が必要です。 ja.vuejs
<script setup>
import { ref } from 'vue'
const state = ref({
hp: 100,
mp: 40,
})
function takeDamage() {
// ref の中のオブジェクトなので、script では .value が必要
state.value.hp -= 10
}
</script>
<template>
<p>HP: {{ state.hp }}</p>
<p>MP: {{ state.mp }}</p>
<button @click="takeDamage">ダメージを受ける</button>
</template>
この場合は、「中のステータス画面に触りたいなら、まず箱を開けてください」というルールが残る感覚です。つまり、外側が reactive か ref かで見え方がかなり変わるのが、今の自分には難しいところでした。
| 「単体だと分かるのに、混ざると急に “あれ、今どっちのルール?” ってなるんだよね」 |
今の自分ルール
ここまで触ってみて、今の自分の中では次のルールで考えると少し整理しやすいです。
- 単体の値や、小さい UI 状態
ref
例: HP バー 1 本、送信中フラグ、モーダル開閉 - 複数の値がまとまった 1 画面ぶんのデータ
reactive
例: ステータス画面、フォーム入力一式 - 迷ったら
「これは 1 本のゲージか?」
「それとも、キャラメイク画面全体か?」
と考える
| 「“1本のバーなら `ref`、1枚の画面なら `reactive`” くらいで持っておくと、最初は十分戦えそう」 |
もちろん、実際にはもっと細かいケースがあると思います。ただ、今の段階では、まずこのくらいの整理で迷子になりにくくするのが大事そうです。
今の理解
まだかなり途中ですが、今の自分の頭の中では「Vue が変更を検知できる形にデータを置くこと」がリアクティビティの中心なのだと思っています。Vue のリアクティビティは、値そのものを魔法のように見ているのではなく、ref や reactive を通して追跡可能な状態を作ることで成り立っています。 ja.vuejs
多分、今の理解を雑にまとめるとこうです。
- 普通の JavaScript の変数
ゲーム内部のメモ帳。値そのものは変えられるけれど、UI 側には自動では伝わらない。 -
ref
1 本のステータスバーや、小さいスイッチ。script側では.valueが必要で、template側では Vue が中身を読んでくれる。 vueframework -
reactive
ステータス画面全体、またはキャラメイクシート全体。オブジェクトとしてまとめて扱いやすい。 ja.vuejs - フォーム画面で考えると
入力値一式はreactive、送信中フラグやエラー表示はref、という分け方が今のところしっくりくる。 - 混ざったとき
reactiveの中のrefは扱いやすくなるけれど、refの中のオブジェクトはまず箱を開ける必要があるので、ここはまだ整理が必要。 vueframework
| 「`template` は“見た目のために少しやさしい世界”、`script` は“仕組みがそのまま見える世界”って考えると、かなり落ち着くね」 |
この見方をすると、普通の変数で画面が変わらなかった理由も少しつながる気がしています。まだ感覚的な理解ですが、今はこのイメージを足場にして進めていくのがよさそうです。
次にやりたいこと
次は、コンポーネントを使いながら「データがどこからどこへ流れるのか」を確認したいです。リアクティブな値を持てるようになってくると、次は「その値を誰が持つのか」「どこへ渡すのか」が気になってきます。
- コンポーネントを分けてみる
画面を部品として切り分けて、1 ファイルに全部書く状態から少し進みたい -
propsを使って親から子へ値を渡す
どこでデータを持って、どこで表示するのかが見えやすくなりそうです -
emitsを使って子から親へイベントを返す
ボタン操作や入力をきっかけに、どのように親側の状態を変えるのか確認したい - フォーム画面をコンポーネントに分けてみる。
たとえば「入力欄」「送信ボタン」「エラーメッセージ表示」を分けると、状態の置き場所が見えやすくなりそうです -
refをどこに置くべきか意識してみる。
1 つのコンポーネントの中に持つのか、親側で持つのかによって、設計の考え方が少し見えてきそうです
| 「そろそろ “値を増やす” から一歩出て、“画面の部品どうしがどう会話するか” を見たくなる頃だね」 |
今回は「クリックしたときに値を更新する」というかなり基本的なところから始めて、ref と reactive の違いをフォーム画面のイメージに寄せながら整理してみました。仕組みを完全に理解したというより、「今はこのイメージで持っておくと迷いにくい」という段階ですが、それでも前よりは Vue の状態の持ち方が見えやすくなってきた気がします。
