vue.jsで「数字」しか入力できないinput要素を作る
[Vue.js]小数や整数しか入力できないinputタグのカスタムコンポーネント
過去に二度、同じことを試行錯誤して記事を書きました
いずれも動作としては問題ないのですが今読み返すと「これってどうなの?」というポイントがあり
改めて考えて今度こそちゃんとした(つもりの)コードを書いてみました
結論
<template>
<input
type="text"
:value="value"
@input="sanitizeAndEmit($event.target.value)"
>
</template>
<script>
export default {
name: 'IntegerStringOnlyInput',
props: {
value: {
type: String,
required: true,
default: '',
},
},
watch: {
// 親からの値変更の時もサニタイズするためにwatch
value(newValue) {
this.sanitizeAndEmit(newValue);
},
},
mounted() {
this.sanitizeAndEmit(this.value);
},
methods: {
sanitizeAndEmit(val) {
// 数字以外を消す & 全角数字を半角数字へ変換
this.$emit(
'input',
val.replace(/[^0-90-9]/g, '').replace(/[0-9]/g, (s) => String.fromCharCode(s.charCodeAt(0) - 0xFEE0)),
);
// サニタイズされた値は直前の値と変わらないので再描画されない 強制的に再描画しないと、表示と実際の値がずれる
this.$forceUpdate();
},
},
};
</script>
使い方としては普通のカスタムコンポーネントと同じで
importしてv-model
で変数をバインドするだけです
<IntegerStringOnlyInput v-model="int" />
過去の記事のコードがなぜよくなかったのか
vue.jsで「数字」しか入力できないinput要素を作る
v-model
と @input
が同時に存在しているから
その記事では
<input
@input="validate"
v-model="numValue"
>
というように書いていました
幸か不幸かこのコードは意図した通りの挙動をしてくれます(通常のv-modelの挙動のあとに値を上書きする)
ではなぜ v-model
と @input
が同時に存在するコードがだめなのか
そもそも v-model
はシュガーシンタックス(※1)なので
つまるところ上記のコードは
<input
@input="numValue = $event.target.value"
@input="validate"
:value="numValue"
>
と同じであると解釈できるのでは、と思いました
(@input
の重複はパースエラーになるので実際にはこのように書くことはできませんが。。。)
そのため、たまたま動くだけで v-model
と @input
が共存することは本来は意図されてない使われ方ではないかと考えています
- もしそうならいつかバージョンが上がった時に動かなくなる可能性があるのではないか
- そうでなくともコードとして同じイベントハンドラが2つあるという状況もあまりよいものではない
と思い、「これはよくない」という結論にいたりました
[Vue.js]小数や整数しか入力できないinputタグのカスタムコンポーネント
$refs
を使っているから
値が変更されず再描画されないために実際の変数の中身と見た目がずれる問題を解消するために
当時は「再描画されないからずれる」ことに気づかず解決しようと $refs
を使ってしまいましたが
そもそも $refs
は使用が推奨されておらず(※2)、使わずに済むならそれに越したことはないため
今回気づいたずれの原因から再描画が解決の糸口なので $forceUpdate()
の方がいいのではという結論に至りました。
単純にコードが冗長
computed
を使ったために無駄にコード量が増え可読性が低下していました
改めて考えてみるとcomputed挟む必要はありませんでした
親コンポーネントからの変更にサニタイズがされない
親コンポーネントから数字以外のものが含まれる値に変更されてもその際には数字以外の文字が取り除かれないため
今回新たに watch
で変更を監視してサニタイズされるようにしました
※1
※2
参考ドキュメント
好みや要件次第で
全角許容する場合は半角への変換をなくしたり
親からの変更時やmount時にサニタイズがされたくない場合は watch
や mounted
を消したりでコントロール可能です