【Vue 3】v-model.trim の動きが textarea タグとコンポーネントで異なる件
Vue 3.2 で textarea
タグをラップした UI コンポーネントを用意して v-model.trim
を使ったところ、入力中に改行や空白文字など突然取り除かれる現象が発生しました:
<script setup>
const msg = ref("");
</script>
<template>
<CustomTextarea v-model.trim="msg" />
</template>
このような現象は textarea
タグで v-model.trim
を使ったときには発生していなかったので、v-model.trim
のドキュメントや実装を確認してどのような仕様となっているかまとめてみました。
textarea
タグに v-model.trim
を使う場合 [1]
-
v-model.trim
による文字列のセット時にはtrim()
メソッド を適用した文字列がセットされる [2] - フォーカスが外れたときに発生する
change
イベント が発生するまでtextarea
要素の入力内容(value
属性の値)に対するtrim()
メソッドの適用を待つ機能がある [3][4]
v-model.trim
を使う場合
コンポーネントに -
v-model.trim
による文字列のセット時にはtrim()
メソッドを適用した文字列がセットされる [5] - カスタムコンポーネントにおける
v-model
はupdate:modelValue
というカスタムイベントを購読するが、input
やchange
といったネイティブイベントは購読しない -
v-model.trim
のように.trim
修飾子(Modifier)を付けてv-model
を使用すると、modelModifiers
という名前の prop で{ trim: true }
のようなオブジェクトが渡される [6]
v-model.trim
の振る舞いを textarea
タグと同じようにするには?
コンポーネントの 親コンポーネントが .trim
修飾子を指定している場合は textarea
タグに .trim
修飾子を付けて対応します:
<script setup lang="ts">
import { computed, type PropType } from "vue";
const props = defineProps({
modelValue: {
type: String,
required: true,
},
modelModifiers: {
type: Object as PropType<{
trim?: true;
}>,
default: () => ({}),
},
});
const emit = defineEmits(["update:modelValue"]);
const data = computed({
get() {
return props.modelValue;
},
set(value) {
emit("update:modelValue", value);
},
});
</script>
<template>
<textarea
v-if="modelModifiers.trim"
v-model.trim="data"
:class="$style.textarea"
/>
<textarea
v-else
v-model="data"
:class="$style.textarea"
/>
</template>
<style lang="scss" module>
.textarea { /* 省略 */ }
</style>
v-if
/ v-else
を用いて v-model.trim
を使うか v-model
を使うかを分岐させているので class
属性などの指定が重複してしまいますが、v-model
の修飾子を動的に設定する方法が見当たらなかったためこの方法をとりました。
なお、textarea
タグに対する v-model.trim
とコンポーネントに対する v-model.trim
によって trim()
メソッドが二重に適用されることとなりますが、trim()
メソッドは冪等[7]であるため許容できるものだと考えています。
その他の修飾子について
.number
修飾子
コンポーネントの v-model.number
の振る舞いと textarea
/ input
タグの v-model.number
(または input[type="number"]
の v-model
) の振る舞いは同じで、parseFloat()
関数で解釈できるものに限って数値をセットします。
.lazy
修飾子
コンポーネントの v-model
で .lazy
修飾子を適用しても動作に変化はなく、textarea
/ input
タグの v-model.lazy
のように振る舞うことはありません。
必要であれば、上記のサンプルコードと同様の対応が必要となります。
-
input
タグでtype="text"
に設定されている場合も同様です ↩︎ -
https://github.com/vuejs/core/blob/v3.2.45/packages/runtime-dom/src/directives/vModel.ts#L50-L60 ↩︎
-
https://github.com/vuejs/core/blob/v3.2.45/packages/runtime-dom/src/directives/vModel.ts#L61-L65 ↩︎
-
https://github.com/vuejs/core/blob/v3.2.45/packages/runtime-dom/src/directives/vModel.ts#L80-L99 ↩︎
-
https://github.com/vuejs/core/blob/v3.2.45/packages/runtime-core/src/componentEmits.ts#L118-L131 ↩︎
-
https://v3.ja.vuejs.org/guide/component-custom-events.html ↩︎
-
任意の値に対して、関数を 1 回適用した結果と 2 回適用した結果が等しい ↩︎
Discussion