解决中文输入法导致的频繁 Input 事件!

大家好,我是 前端架构师 - 大卫

更多优质内容请关注微信公众号 @程序员大卫

初心为助前端人🚀,进阶路上共星辰✨,

您的点赞与关注❤️,是我笔耕不辍的灯💡。

背景

在使用 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>

在上图的场景中,我们输入中文时,明明还没确认文字,但控制台已经打印了输入内容。

解决方案

要解决这个问题,可以利用 compositionstartcompositionend 事件:

  • 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 自定义事件绑定,手动管理输入法的 compositionstartcompositionendinput 事件。

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 中,都可以通过封装一个通用的输入组件来解决这一问题,从而避免中文输入过程中出现的"值被提前更新"的情况。这样既能保持输入体验的流畅性,也能确保数据的准确性。

相关推荐
水星记_12 小时前
时间轴组件开发:实现灵活的时间范围选择
前端·vue
2501_9301247012 小时前
Linux之Shell编程(三)流程控制
linux·前端·chrome
潘小安12 小时前
『译』React useEffect:早知道这些调试技巧就好了
前端·react.js·面试
@大迁世界13 小时前
告别 React 中丑陋的导入路径,借助 Vite 的魔法
前端·javascript·react.js·前端框架·ecmascript
EndingCoder13 小时前
Electron Fiddle:快速实验与原型开发
前端·javascript·electron·前端框架
EndingCoder13 小时前
Electron 进程模型:主进程与渲染进程详解
前端·javascript·electron·前端框架
Nicholas6813 小时前
flutter滚动视图之ScrollNotificationObserve源码解析(十)
前端
@菜菜_达14 小时前
CSS scale函数详解
前端·css
想起你的日子14 小时前
Vue2+Element 初学
前端·javascript·vue.js
原生高钙14 小时前
一文了解 WebSocket
前端·面试