【Vue.js】v-model解体新書
前書き
こんな人向け
-
v-model
は脳死で使っている。 - 独自コンポーネントで使おうとしたらなんかエラーが出た。
- 数値入力は
v-model.number
で完璧だと信じている。 -
v-model
完全に理解した。
上記は全て著者のことです。「半端な覚悟でv-model
を語るんじゃねぇぞ!」と思った方は回れ左。(どっち回りでも変わらんよな)
半年くらいVue
に触れてきましたが、ずっとなんとなくで使っていました。ですが、最近ようやく重い腰をあげたので、ここにまとめておこうと思います。
本編
1. そもそもv-modelって何者?
早速、公式先生から引用
form の input 要素 や textarea 要素、 select 要素に双方向 (two-way) データバインディングを作成するには、v-model ディレクティブを使用することができます。
うん?わかったようなわからないような・・・。まぁ、噛み砕いて説明を。
そもそも、v-model
の役割は「変更とデータを紐づけること」です。それを実現するには、①表示するデータ、②変更があればデータに反映する、という2つが必要ですね。その2つをセットにしたのが双方向 (two-way) データバインディング
というわけです。
使い方はこんな感じ。
<template>
<input v-model="username" />
<p>username: {{ username }}</p>
</template>
<script>
// 初期値として username が input に渡される
// input の変更が username に反映される
export default {
data: () => {
return {
username: "username"
};
}
}
</script>
ご覧の通り、入力欄とデータが双方向にリンクして連動しているのが分かるかと思います。
「これだけ?」と思うかもしれませんが、「これだけ」でございます、ハイ。
・・・流石に「これだけ」ではタイトル詐欺ですのでもう少し深掘りしていきます。
2. v-modelを解剖しよう
またまた公式から引用
v-model はユーザーの入力イベントにおいてデータを更新するための基本的な糖衣構文 (syntax sugar) で、それに加えて、いくつかのエッジケースに対しては特別な配慮をしてくれます。
v-model
は糖衣構文、つまりは何かの省略記法、v-model
という機能では無くて、複数の機能の組み合わせでできてますよ、とのこと。
1章の「①表示するデータ、②変更があればデータに反映するの組み合わせ」は要素ごとに以下のように決められています。(公式から引用)
- テキストと複数行テキストは、value プロパティと input イベントを使用します
- チェックボックスとラジオボタンは、checked プロパティと change イベントを使用します
- 選択フィールドは、value プロパティと change イベントを使用します
1章の例は一つ目の「テキスト」ですので、value
とinput
に展開することができます。
<template>
<input v-model="username" />
<input :value="username" @input="username = $event.target.value" />
<p>username: {{ username }}</p>
</template>
<script>
// v-model と value-input が全く同じ挙動をする
export default {
data: () => {
return {
username: "username"
};
}
}
</script>
3. コンポーネントでの使い方
恐らく、この記事にたどり着いた方は自作コンポーネントで v-model
をどう扱えばいいのかを調べていたのではないでしょうか?というか、そうじゃないと v-model
を深掘りしようとは思わないですよね。
自作コンポーネント内で v-model
を使おうとして「 props
で渡してその値を v-model
に代入してしまえばいいだけでは? 」と思っていて、
Vue
先生に怒られた経験があるのではないでしょうか?
これは、 Vueコンポーネントの props
が一方通行であるためですが、
2章の内容をうまく使ってあげるとこの問題が解決できます。
<template>
<input
:value="value"
@input="$emit('input', $event.target.value)"
/>
</template>
<script>
export default {
props: ['value']
}
</script>
<template>
<div>
<child v-model="username"></child>
</div>
</template>
<script>
import Child from "@/components/child";
export default {
components: {
Child
},
data() {
return {
username: "username"
}
}
}
</script>
解説すると
- 親コンポーネントから子コンポーネントに対しての
v-model
は:value
と@input
に解釈される - 子コンポーネントの
<input>
にvalue
(username) が渡る - 子コンポーネントの
<input>
で入力があればemit
で親のv-model
に$event.target.value
が渡ることでusernameに値が反映される
という流れですね。
props
自体を変更せず、変更されることを emit
で親に知らせてあげればいいわけです。
あとがき
- 分かればなんてことないですが、そこまで結構詰まりました・・・。もっと実装例があれば、スッと理解できたのかもなぁとも思いました。
(自分でやれ)