v-modelディレクティブに.number修飾子を添えたとき、データは変わっているのにcomputedに定めた算出プロパティが呼び出されなかったケースについてのご説明です。
v-model.numberの役割
v-model.numberにバインディングしたデータは、数値に変換されます。HTML 5.1には<input type="number">が備わりました。けれど、できることは値が数値であるかどうかの検証です。value属性値はあくまで文字列で、数値になるわけではありません。
v-model.numberを用いれば、<input type="text">であっても、データは数値に変わります。ただし、文字の入力は可能ですし、数値にできないときデータに与えられるのは、そのままの文字列です。
v-model.numberにバインディングした値が数値であることを確かめる
そこで、v-model.numberにバインディングした値が数値でなかったら、警告を示すようにしましょう。つぎのコード001は、v-showディレクティブに与えた算出プロパティ(notNumber )で警告メッセージの表示・非表示を切り替えます。
コード001■v-model.numberにバインディングした値が数値でなければ警告を示す
<div id="app">
<input v-model.number="inputNumber" type="text">
<p v-show="notNumber">数値ではありません</p>
</div>
const app = new Vue({
data: {
inputNumber: null
},
computed: {
notNumber() {
const value = Number(this.inputNumber);
console.log(value); // 確認用
return Number.isNaN(value);
}
},
});
document.addEventListener('DOMContentLoaded', () =>
app.$mount('#app')
);
動きはCodePenに掲げたつぎのサンプル001でお確かめください。数値を入れれば警告は出ません。いったん入力は消して、文字を打ち込むと直ちにメッセージが示されます。ところが、数値のあとに続けて文字を加えても(たとえば「123ab」)、警告が表れないのです。
サンプル001■Vue.js: v-model.number prevents computed property from being called when characters are inputted after number
See the Pen Vue.js: v-model.number prevents computed property from being called when characters are inputted after number by Fumio Nonaka (@FumioNonaka) on CodePen.
算出プロパティはいつ更新されるか
computedオプションに定めた算出プロパティは「リアクティブな依存関係が更新されたときにだけ再評価されます」(「算出プロパティ vs メソッド」参照)。もっとも、前傾コード001の算出プロパティ(notNumber)が参照しているのは、まさにv-modelでバインディングしたデータ(inputNumber)です。現に、単純な数値や文字、あるいははじめに文字を入れた場合には、正しく警告メッセージの表示は切り替わります。
ここで改めて公式ガイドの.number修飾子の項を見ると、つぎのような説明があります。つまり、数値への変換は、内部的にparseFloat()関数で行われているようです。
もし値が
parseFloat()で解釈できない場合は、もとの値が返却されます。
前傾コード001では、Number()関数で文字列を数値に直しました(「数字を数値に変換する」参照)。実は、ふたつの関数の処理は、つぎのような場合には異なるのです。
parseFloat('123ab') // 123
Number('123ab') // NaN
ここから推測されるのは、v-model.numberでバインディングされたデータは、parseFloat()関数で数値変換してから算出プロパティの再評価を決めているのではないかということです。数値につづけて入れた文字は、parseFloat()関数は取り払ってしまいます。そのため、値に変化がないとみなされて、算出プロパティは呼び出されないと考えればつじつまが合いそうです。
今回の例では、.number修飾子を外してしまえば、警告メッセージは正しく表示されます。ただし、バインディングしたデータは、数値変換されない文字列のままです。別途v-modelを、v-bind:value(省略表記:value)とv-on:input(省略表記@input)に分けて扱うことなどの検討が要るでしょう(「コンポーネントでv-modelを使う」参照)。