LoginSignup
5
9

More than 3 years have passed since last update.

[今度こそ完全に理解した]vueで数字しか入力できないinputタグのカスタムコンポーネントを作る

Last updated at Posted at 2020-05-31

vue.jsで「数字」しか入力できないinput要素を作る
[Vue.js]小数や整数しか入力できないinputタグのカスタムコンポーネント

過去に二度、同じことを試行錯誤して記事を書きました
いずれも動作としては問題ないのですが今読み返すと「これってどうなの?」というポイントがあり
改めて考えて今度こそちゃんとした(つもりの)コードを書いてみました

結論

IntegerStringOnlyInput.vue
<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時にサニタイズがされたくない場合は watchmounted を消したりでコントロール可能です

5
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
9