Vuetify3のv-text-fieldのv-modelは、IME入力中にも更新される

Vue.js3のテキストボックスのv-modelは、日本語入力中などのIME入力中は値が更新されません。これは、ドキュメントにも記載されています。
参考:フォーム入力バインディング | Vue.js

IME を必要とする言語 (中国語、日本語、韓国語など) では、IME による入力中に v-model が更新されないことに気づくでしょう。 もしこれらの更新にも対応したい場合は、 v-model の代わりに input イベントリスナーと value バインディングを使用してください。

ただ、Vue.jsで人気のUIフレームワークであるVuetifyのテキストボックスコンポーネントであるv-text-fieldは、日本語入力中でも値が更新されるようです。
試しに、<input type=”text”>とv-text-fieldを横に並べ、どちらも同じrefオブジェクト変数をv-modelに入れた画面を作成して試してみました。

<script setup lang="ts">
const text = ref('')
</script>

<template>
  <v-row>
    <v-col cols="12">
      <p><input type="text">と<v-text-field>のv-modelの動き比較(日本語入力の際の動きが異なる)</p>
    </v-col>
  </v-row>
  <v-row>
    <v-col cols="auto">
      <input type="text">:
    </v-col>
    <v-col cols="3">
      <input v-model="text" type="text" style="border:1px solid black" class="pa-1 w-100">
    </v-col>
    <v-col cols="auto">
      <v-text-field>:
    </v-col>
    <v-col cols="3">
      <v-text-field
        v-model="text"
        class="w-100"
        density="compact"
        hide-details
      />
    </v-col>
  </v-row>
</template>

左側のテキストボックス(<input type=”text”>)は日本語入力確定時に右側のテキストボックス(v-text-field)に反映されますが、右側のテキストボックスの入力時は、日本語入力中でも左側のテキストボックスに反映されてます。

人によってはこれでも問題ないかもしれませんが、入力中の確定していない文字列まで値に入ってしまうのは問題があることもあります。例えば、入力できるのは半角英数字のみにしたいといった場合、入力中にも値が反映されてしまうと実装方法によってはうまく動きません。

<script setup lang="ts">
const text = ref('')

watch(text, async (newVal, oldVal) => {
  // 半角に変換
  newVal = newVal.replace(/[A-Za-z0-9]/g, (s) => {
    return String.fromCharCode(s.charCodeAt(0) - 0xFEE0)
  })

  if (!/^[a-zA-Z0-9]*$/.test(newVal)) {
    // 半角英数字のみでなければ、元の値にする
    newVal = oldVal
  }

  // 値が変わっていれば変更
  if (text.value !== newVal) {
    const activeInput = document.activeElement as HTMLInputElement
    // カーソル位置を覚えておく
    const backwardCursorPos = activeInput.value.length - (activeInput.selectionEnd ?? activeInput.value.length)

    await nextTick()
    text.value = newVal
    // テキストボックスに書き換えた値反映
    await nextTick()
    // カーソル位置が最後になっているので変更する
    const cursorPosition = activeInput.value.length - backwardCursorPos
    activeInput.setSelectionRange(cursorPosition, cursorPosition)
  }
})
</script>

<template>
  <v-row>
    <v-col cols="12">
      <p>半角英数字のみ許可</p>
    </v-col>
  </v-row>
  <v-row>
    <v-col cols="auto">
      <input type="text">:
    </v-col>
    <v-col cols="3">
      <input v-model="text" type="text" style="border:1px solid black" class="pa-1 w-100">
    </v-col>
    <v-col cols="auto">
      <v-text-field>:
    </v-col>
    <v-col cols="3">
      <v-text-field
        v-model="text"
        class="w-100"
        density="compact"
        hide-details
      />
    </v-col>
  </v-row>
</template>

上記は入力した値が、全角英数字の部分を半角英数字にしたうえで、半角英数字以外の文字があれば値を更新しないという動作にしたサンプルです。左側のテキストボックスでの入力では問題なく動いていますが、右側は動きがおかしくなります(日本語を入力すると値が消えていきます)。

この対処としては、v-text-fieldではv-modelを使わずに、inputイベントとcompositionendイベントで値を更新するようにすることが考えられます。

<v-text-field
  :model-value="text"
  class="w-100"
  density="compact"
  hide-details
  @input="($ev: InputEvent) => {
    if (!$ev.isComposing){
      // 日本語入力中でなければ値更新
      text = ($ev.target as HTMLInputElement).value
    }
  }"
  @compositionend="($ev: CompositionEvent) => {
    // 入力確定時は値更新
    text = ($ev.target as HTMLInputElement).value
  }"
/>

Vue.js本体がどのような動きになっているのか分かりませんが、多分こんな動きになっているものと思われます。

ちなみに、Vue.jsのテキストボックスとv-text-fieldの動きと異なるものとしては、type=”number”を指定した際に、入力した値の型がnumberになるかstringになるかという違いもあります。

<script setup lang="ts">
const num = ref<number | string>('')
</script>

<template>
  <v-row>
    <v-col cols="12">
      <p>入力値の型:{{ typeof num }}</p>
    </v-col>
  </v-row>
  <v-row>
    <v-col cols="auto">
      <input type="number">:
    </v-col>
    <v-col cols="3">
      <input v-model="num" type="number" style="border:1px solid black" class="pa-1 w-100">
    </v-col>
    <v-col cols="auto">
      <v-text-field>:
    </v-col>
    <v-col cols="3">
      <v-text-field
        v-model="num"
        type="number"
        class="w-100"
        density="compact"
        hide-details
      />
    </v-col>
  </v-row>
</template>

<input type=”number>でも、値が空の場合はstring型になるようです。

こんな感じで、UIフレームワークを使うと、思わぬところで挙動が違っていたりして、つまずくことがあります。カスタマイズ性も低かったりするので、選定には気を付けた方がいいとかもしれないです。

今回作成したプログラムの動作ページ:inputタグとVuetify3のv-text-fieldのv-modelの違い比較

コメント

タイトルとURLをコピーしました