你的 Vue 事件修饰符 .enter 用对了吗

217 阅读3分钟

背景

大家好,我是 Canmick。前段时间在公司开发了一个 AI 对话机器人,刚上线就收到了一个 Bug,说是输入中文时按回车键会直接发送对话。

看到描述的一瞬间,觉得是中文输入法兼容性问题,因为代码里使用的是原汁原味的 Vue 键盘事件修饰符 @keydown.enter,不应该有问题呀。

带着疑问 debugger 了一下代码,果然...与输出法无关,不管输入法是否键入中,只要按下回车键就会触发 @keydown.enter ,好低级的错误呀,接着看看如何处理。

问题分析

使用英文输入法时,正常键入字母符号不会弹出文字选框,此时键入和回车暂未发现冲突。

使用中文输入法时,以拼音为例,键入拼音时会出现文字选框,一般以空格键确认文字选择,此时键入与回车暂未发现冲突;

但存在文字选框时直接按下回车键,冲突就来了,正在输入的拼音会以字母形式键入输入框,同时触发输入框的回车事件,问题复现。

image.png

核心问题是中文输入法的文字选框回车事件和输入框回车事件冲突,解决思路就是触发输入框回车事件时,判断是否处于文字选框状态,是就不执行输入框的回车事件逻辑。

那这个输入法的文字选框是个什么东西?又如何判断是否处于文字选框呢?

其实这个文字选框又叫输入法编辑器 Input Method Editor,简称 IME,可以将用户多个输入单元合成为目标文字。

以下为 MDN 的描述,可以看出不仅中文需要IME,日文、韩文、拉丁字也需要。 image.png

至于 IME 状态,HTML 规范提供了相关接口,分别是

  • CompositionEvent

    • compositionstart
    • compositionupdate
    • compositionend
  • KeyboardEvent

    • isComposing

前因后果已然清晰,事不宜迟,开始编码。

解决方案

判断IME最省事的方法就是 KeyboardEventisComposing,以此展开

方案1:在每个事件回调中添加判断

  • 优点:快速解决问题上线
  • 缺点:无法复用
const handleKeydown = (e) => {
    if(e.isComposing) return;
    // other logic
}

方案2:自定义 Input 组件

  • 优点:较高复用性
  • 缺点:旧业务较替换新组件存在一定成本
<script setup lang="ts">
defineOptions({
  name: 'MyInput'
})

const emit = defineEmits(['enter'])
// 必须是 keydown 事件,keyup.enter 的 isComposing 已经为 false
const handleKeydownEnter = (e: KeyboardEvent) => {
  if (!e.isComposing) {
    emit('enter', e)
  }
}
</script>

<template>
  <input type="text" @keydown.enter="handleKeydownEnter" />
</template>


// 使用
<MyInput @enter="handleEnter"/>

方案2:自定义指令 v-enter

  • 优点:较灵活
  • 缺点:...
export const VEnterDirective = {
  // 在绑定元素的父组件及他自己的所有子节点都挂载完成后调用
  mounted(el, binding) {
    el._enterCallback = (e) => {
      if (e.keyCode === 13 && !e.isComposing && typeof binding.value === 'function') {
        binding.value(e)
      }
    }
    el.addEventListener('keydown', el._enterCallback)
  },
  // 绑定元素的父组件卸载前调用
  beforeUnmount(el) {
    el.removeEventListener('keydown', el._enterCallback)
    delete el._enterCallback
  }
}
// 注册
const app = createApp({}) 
app.directive('enter', VEnterDirective)

// 使用
<input type="text" v-enter="handleKeydownEnter" />

小结

输入法输入时弹出的悬浮窗口叫做输入法编辑器IME,主要作用是合成文字,例如合成中文、日文、韩文、拉丁字等。

vue 的@keydown.enter 不会判断当前是否存在IME,导致中文输入过程按回车键时,@keydown.enter也会同时触发。

解决该问题可以利用 KeyboardEventisComposing,更优方案可以封装 Input 组件暴露 @enter 事件或封装自定义指令 v-enter