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を適切に(効果的に?)使うカスタムフォームの実例が現時点では思い浮かばないですが、きっと世の中にはそういった例も溢れているのだろうと信じつつ、そろそろ公式ガイド以外のプラクティスを眺めていきたいと思います。