大家好,我是 前端架构师 - 大卫。
更多优质内容请关注微信公众号 @程序员大卫。
初心为助前端人🚀,进阶路上共星辰✨,
您的点赞与关注❤️,是我笔耕不辍的灯💡。
背景
在使用 input
输入框时,如果正在通过中文输入法打字,会出现一个问题:输入还未完成(比如拼音还没选字、还没按下回车)时,input
事件就已经触发了。 这会导致我们在还没真正确认输入内容时,数据就被提前更新了。
示例代码如下:
html
<script setup lang="ts">
const onInput = (e: Event) => {
const value = (e.target as HTMLInputElement).value;
console.log(value);
};
</script>
<template>
<input @input="onInput" />
</template>

在上图的场景中,我们输入中文时,明明还没确认文字,但控制台已经打印了输入内容。
解决方案
要解决这个问题,可以利用 compositionstart
和 compositionend
事件:
compositionstart
:中文输入法开始输入时触发compositionend
:中文输入法输入结束(确认文字)时触发
因此,我们可以在输入法开始时设置一个标记(flag),在输入完成前不触发 input
逻辑;等输入结束时,再统一更新一次值。
Vue3 封装组件示例
下面演示一个在 Vue3 中封装的输入组件。
说明:这里的
update:modelValue
是为了保证组件能正常配合v-model
使用。modelValue
的类型为String | Number
,参考了 Element-Plus 的实现。
html
<script setup lang="ts">
import { ref, useAttrs } from "vue";
const props = defineProps({
modelValue: {
type: [String, Number],
},
});
defineOptions({
inheritAttrs: false,
});
const emit = defineEmits<{
compositionstart: [e: CompositionEvent];
compositionend: [e: CompositionEvent];
input: [value: string | number];
"update:modelValue": [value: string | number];
}>();
const attrs = useAttrs();
const isComposing = ref(false);
const updateValue = (e: CompositionEvent | Event) => {
const value = (e.target as HTMLInputElement).value;
emit("update:modelValue", value); // 保证 v-model 正常工作
emit("input", value);
};
// 输入法开始
const onCompositionstart = (e: CompositionEvent) => {
emit("compositionstart", e);
isComposing.value = true;
};
// 输入法结束
const onCompositionend = (e: CompositionEvent) => {
emit("compositionend", e);
isComposing.value = false;
updateValue(e);
};
// 非中文输入时触发
const onInput = (e: Event) => {
if (isComposing.value) return;
updateValue(e);
};
</script>
<template>
<input
v-bind="attrs"
:value="props.modelValue"
@compositionstart="onCompositionstart"
@compositionend="onCompositionend"
@input="onInput"
/>
</template>
父组件调用方式(Parent.vue
):
html
<script lang="ts" setup>
import InputIME from "./components/InputIME.vue";
const onInput = (value: string | number) => {
console.log(value);
};
</script>
<template>
<InputIME @input="onInput" />
</template>
Vue2 封装组件示例
在 Vue2 中,也可以采用类似的思路:
说明:通过
inheritAttrs: false
阻止默认事件透传,再利用computed
中的inputListeners
自定义事件绑定,手动管理输入法的compositionstart
、compositionend
和input
事件。
html
<script>
export default {
inheritAttrs: false,
props: {
value: [Number, String],
},
data() {
return {
isComposing: false,
};
},
computed: {
inputListeners() {
return {
...this.$listeners,
input: (e) => {
if (this.isComposing) return;
this.updateValue(e);
},
compositionstart: (e) => {
this.$emit("compositionstart", e);
this.isComposing = true;
},
compositionend: (e) => {
this.$emit("compositionend", e);
this.isComposing = false;
this.updateValue(e);
},
};
},
},
methods: {
updateValue(e) {
const value = e.target.value;
this.$emit("input", value);
},
},
};
</script>
<template>
<input :value="value" v-bind="$attrs" v-on="inputListeners" />
</template>
父组件调用方式(Parent.vue
):
html
<script>
import InputIME from "./components/InputIME.vue";
export default {
components: {
InputIME,
},
methods: {
onInput(value) {
console.log(value);
},
},
};
</script>
<template>
<InputIME @input="onInput" />
</template>
总结
无论是在 Vue3 还是 Vue2 中,都可以通过封装一个通用的输入组件来解决这一问题,从而避免中文输入过程中出现的"值被提前更新"的情况。这样既能保持输入体验的流畅性,也能确保数据的准确性。