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

相关推荐
Ting-yu几秒前
Nginx快速入门
java·服务器·前端·nginx
我是日安5 分钟前
从零到一打造 Vue3 响应式系统 Day 17 - 性能处理:无限循环
前端·vue.js
user94051035547179 分钟前
Uniapp 3D 轮播图 轮播视频 可循环组件
前端
前端付豪9 分钟前
12、为什么在 <script> 里写 export 会报错?
前端·javascript
Junsen12 分钟前
electron窗口层级与dock窗口列表
前端·electron
一个小潘桃鸭13 分钟前
需求:el-upload加上文件上传进度
前端
梦醒繁华尽15 分钟前
使用vue-element-plus-x完成AI问答对话,markdown展示Echarts展示
前端·javascript·vue.js
鹏多多39 分钟前
关于React父组件调用子组件方法forwardRef的详解和案例
前端·javascript·react.js
吃饺子不吃馅1 小时前
AntV X6 核心插件帮你飞速创建画布
前端·css·svg
Ares-Wang1 小时前
Vue2 》》Vue3》》 Render函数 h
javascript