Vue — v-model
テクノロジ系 / L01 template-syntax — テンプレート構文
Vue — v-model
React との大きな差
// React (双方向の手書き)
<input value={text} onChange={e => setText(e.target.value)} />
// Vue (v-model 1つで完結)
<input v-model="text" />v-model = :value + @input の シュガー。
各要素での挙動
<script setup>
const text = ref("");
const checked = ref(false);
const selected = ref("");
const colors = ref([]);
</script>
<template>
<input v-model="text" /> <!-- 文字列 -->
<input type="checkbox" v-model="checked" /> <!-- 真偽 -->
<select v-model="selected"> <!-- 文字列 -->
<option value="a">A</option>
<option value="b">B</option>
</select>
<input type="checkbox" value="red" v-model="colors" /> <!-- 配列 -->
<input type="checkbox" value="blue" v-model="colors" />
</template>要素ごとに自動で適切な属性 (value / checked) に紐づく。
v-model modifier
<template> <!-- .lazy: input でなく change で同期 --> <input v-model.lazy="text" /> <!-- .number: 自動で数値化 --> <input v-model.number="age" type="number" /> <!-- .trim: 前後空白除去 --> <input v-model.trim="email" /> </template>
.number は特に頻出。type="number" でも文字列で来るので必須。
textarea / radio
<template> <textarea v-model="message"></textarea> <input type="radio" value="cat" v-model="animal" /> <input type="radio" value="dog" v-model="animal" /> </template>
radio は同じ v-model を共有 → どれかが選ばれている。
TypeScript
<script setup lang="ts">
import { ref } from "vue";
const text = ref<string>("");
const age = ref<number>(0);
const tags = ref<string[]>([]);
</script>
<template>
<input v-model="text" />
<input v-model.number="age" type="number" />
</template>ref の型注釈で型安全。
コンポーネントでも v-model
L02 ch02 で詳しく:
<MyInput v-model="text" />
→ 子側で defineModel() で受け取り。
独自コンポーネントでも双方向が書ける。
まとめ
v-model= 双方向バインディング (:value+@inputのシュガー)- 要素タイプによって自動で適切な属性に
- modifier:
.lazy / .number / .trim - TS では
ref<T>(...)で型注釈 - コンポーネントの v-model は L02 で
L01 完。次は L02 コンポーネント設計。
考えてみよう
既定では正解・不正解の判定はしません。自分のペースで「答えを見る」を。採点してほしい場合だけ「採点してほしい」を押してください。
Lv1: 基本 — input / textarea / select / checkbox / radio
問題
<script setup lang="ts">
const text = ref("");
const bio = ref("");
const role = ref("user");
const agree = ref(false);
const color = ref("red");
const tags = ref<string[]>([]);
</script>
<template>
<input v-model="text" placeholder="text" />
<textarea v-model="bio" />
<select v-model="role">
<option value="user">User</option>
<option value="admin">Admin</option>
</select>
<input type="checkbox" v-model="agree" />
<input type="radio" value="red" v-model="color" /> Red
<input type="radio" value="blue" v-model="color" /> Blue
<input type="checkbox" value="sale" v-model="tags" /> Sale
<input type="checkbox" value="new" v-model="tags" /> New
<pre>{{ JSON.stringify({ text, bio, role, agree, color, tags }, null, 2) }}</pre>
</template>合格基準
- 6種類の入力が双方向動作
- React と比べて記述が短いことを実感
Lv2: 応用 — modifier
問題
<script setup>
const trimmed = ref("");
const numeric = ref(0);
const lazy = ref("");
</script>
<template>
<input v-model.trim="trimmed" placeholder="trim" />
<input v-model.number="numeric" type="number" placeholder="number" />
<input v-model.lazy="lazy" placeholder="lazy (change で更新)" />
<p>trimmed: "{{ trimmed }}" (前後空白なし)</p>
<p>numeric: {{ numeric }} (typeof: {{ typeof numeric }})</p>
<p>lazy: "{{ lazy }}"</p>
</template>各 modifier の効果を確認。
合格基準
- .trim: 前後空白除去
- .number: 数値化
- .lazy: フォーカスアウトで更新
Lv3: 発展 — 子コンポーネントで v-model
問題
子コンポーネント MyInput を作って親から v-model。
<!-- MyInput.vue (Vue 3.4+) -->
<script setup lang="ts">
const model = defineModel<string>();
</script>
<template>
<input v-model="model" />
</template>
<!-- 親 -->
<script setup>
const text = ref("");
</script>
<template>
<MyInput v-model="text" />
<p>{{ text }}</p>
</template>子の input で打つと親の text が更新される。
合格基準
- 親子間で v-model 動く
- defineModel が defineProps + defineEmits を集約していることを確認
提出
submission.md に3問の回答を。