Vue.jsの公式ガイドにある**カスタムイベントを使用したフォーム入力コンポーネントのサンプル「とても簡単な通貨入力」**というところで、??となりました。
というわけで、そのサンプルの流れを追ってみました。(Vue.jsを数日前に初めて触れた初心者が書きます)
なにはともあれ、サンプル
とても簡単な通貨入力で、実行して見てみましょう
公式ガイド上では「とても簡単な」だそうです。
<currency-input v-model="price"></currency-input>
Vue.component('currency-input', {
template: '\
<span>\
$\
<input\
ref="input"\
v-bind:value="value"\
v-on:input="updateValue($event.target.value)">\
</span>\
',
props: ['value'],
methods: {
// 値を直接的に更新する代わりに、このメソッドを使用して input の
// 値の整形と値に対する制約が行われる
updateValue: function (value) {
var formattedValue = value
// 両端のスペースを削除
.trim()
// 小数点2桁以下まで短縮
.slice(
0,
value.indexOf('.') === -1
? value.length
: value.indexOf('.') + 3
)
// 値が既に正規化されていないならば、
// 手動で適合するように上書き
if (formattedValue !== value) {
this.$refs.input.value = formattedValue
}
// input イベントを通して数値を発行する
this.$emit('input', Number(formattedValue))
}
}
})
理解のポイント
以下の説明に尽きると思います。
<input v-model="something">
これは以下の糖衣構文(シンタックス・シュガー)である、と。
<input
v-bind:value="something"
v-on:input="something = $event.target.value">
わからなかったポイント
コンポーネントと共に使用されるとき、これは簡単にできます:
実はこの日本語がちょっと理解できなくて、英語ガイドを読んでみたのですが、
When used with a component, this simplifies to:
...特段ヘンな訳し方をしているわけでもなさげでした。。
<custom-input
:value="something"
@input="value => { something = value }">
</custom-input>
そのため、コンポーネントを v-model と共に動かすためには、以下が必要です (これらは 2.2.0 以降で設定することができます):
・value
プロパティを受け入れる
・ 新しい値と共にinput
イベントを送出する
いったいぜんたい、何を言っているのでしょうか...。
サンプルを動かせるように不足を補う
Vue.jsの本体を読み込むのと、<script>
タグで囲いつつ、Root Vue Instance(だっけ?)を生成(new)します。
<script src="https://unpkg.com/vue"></script>
<div id="currency-input-example">
<currency-input v-model="price"></currency-input>
</div>
<script>
Vue.component('currency-input', {
// 略...サンプルそのまま
})
new Vue({
el: '#currency-input-example',
data: { price: '' }
})
</script>
流れを追ってみる
1. ユーザがフォームに文字を入力すると
<currency-input>
コンポーネントの<input>
タグにある、以下のメソッドが動きます(一文字入力するごとに動く)。
尚、引数の$event.target.value
が入力した文字(群)です。
v-on:input="updateValue($event.target.value)"
2. updateValue
メソッド
入力した文字(群)value
を.trim()
してslice()
した値がformattedValue
となります。
formattedValue = value.trim().slice(0, なんとかかんとか)
-
trim()
: 入力した文字(群)の両端のスペースを排除 -
slice(0, N)
: 引数に指定した桁数(0からNまで)で文字列を切る
ここで、.slice(0, N)
の終端位置を示すN
は、入力文字によって動的に変わります(三項演算子で!)。
3. 三項演算子
value.indexOf('.') === -1 ? value.length : value.indexOf('.') + 3
入力した文字(群)value
がドット(‘.’)が含まれているかどうかで変わります。
- 含まない(-1): 入力した文字(群)の桁数をそのまま返す
- 含む: ドット(‘.’)の出現位置 + 3 を返す
つまり、ドット(‘.’)を含まない場合は入力した文字(群)の桁数をそのまま返し、含む場合は小数第三位以下を切り捨てた値を返すことになります。
Ex) 入力文字 => sliceされた文字
- 1 => 1
- 1.2 => 1.2
- 1.22 => 1.22
- 1.222 => 1.22
4. $refs
によって子コンポーネントを参照
$refs
は、Vue.js - $refs によると、
ref によって登録された子コンポーネントを保持するオブジェクトです。
また、ref
はVue.js - refによると、
ref は要素または子コンポーネントに参照を登録するために使用されます。参照は親コンポーネントの $refs オブジェクトのもとに登録されます。プレーンな DOM 要素に使用する場合は、参照はその要素になります。子コンポーネントに使用する場合は、参照はコンポーネントインスタンスになります
よくわからないですが、this.$refs
はこのコンポーネントより下層にある要素内において、ref
属性が指定されているDOMを参照できる(ように見えます)。
今回の場合は、<span><input ref=“input” v-bind:value...></span>
となっていて、this
は<span>
を指すので、下層にある<input ref="input">
タグのDOMをthis.$refs.input
で参照でき、更にその中のvalue
を取得できる、と。
if (formattedValue !== value) {
this.$refs.input.value = formattedValue
}
というわけで、上記はsliceされた文字(群)と入力した文字(群)を比較し、不一致であれば、$refs
のinput
のvalue
にformattedValue
を代入する、ということになります。
5. 最後に親コンポーネントのinput
をトリガーする
this.$emit('input', Number(formattedValue))
Number(formattedValue)
を引数として、'input'
イベントを発火させると…
ここからが謎だったポイント
繰り返しになりますが、
コンポーネントと共に使用されるとき、これは簡単にできます:
<custom-input
:value="something"
@input="value => { something = value }">
</custom-input>
つまり、今回の例に当てはめると、
<currency-input v-model="price"></currency-input>
これは、以下と同じ(糖衣構文?)だと。
(最初からそう言ってんじゃんと言われればそれまでですが...。)
<currency-input
v-bind:value="price"
v-on:input="argument => { price = argument }">
</currency-input>
なので、
-
value
プロパティを受け入れる - 新しい値と共に
input
イベントを送出する
が必要だったわけです。
6. 話を流れを追うところに戻して
this.$emit('input', Number(formattedValue))
Number(formattedValue)
を引数として、'input'
イベントをトリガーする$emit
によって、
<currency-input
v-bind:value="price"
v-on:input="argument => { price = argument }">
</currency-input>
price
にargument
であるNumber(formattedValue)
が代入され、最終的にv-bind:value=“price”
により、value
にNumber(formattedValue)
がバインドされるというわけです。
感想
正直、このv-model
を適切に(効果的に?)使うカスタムフォームの実例が現時点では思い浮かばないですが、きっと世の中にはそういった例も溢れているのだろうと信じつつ、そろそろ公式ガイド以外のプラクティスを眺めていきたいと思います。