中文输入法在input的表现引起异常?且听我娓娓道来

432 阅读3分钟

image.png

产生的背景:

我接到了一个需求,要求在一个表单里输入标签,每个标签不超过 5 个字,不得超过 5 个标签。
看着很简单对吧? 我起初也是这么想的,于是利用el-select其中的filter-method写了如下方法:

handleLabelsChange(value) {
      if (this.form.subjectiveLabels.length >= 5) {
        return
      }
      let labels = value + ''
      if (labels.length > 5) {
        labels = labels.slice(0, 5)
        this.form.subjectiveLabels.push(labels)
      }
    },

乍一看好像没什么问题,如果标签的长度超出 5 个,不用管。如果正在输入的字符超出了 5 个,那么截取前 5 个字符,将它作为一个标签加入标签的数组中。
看上去好得很啊,那让我们运行一下吧:

  • 输入字符串cccccc和数字123456,结果正常出现内容:

image.png

image.png

  • 现在让我们说中文,输入我是火车王,结果如下:

image.png

什么情况?直接出现了 2 条数据! 但是英文字符和数字明显不会有这个问题,那么问题就一定出在了中文这里。
那么输入英文的时候和输入中文到底有什么区别呢?

英文输入法和中文输入法的区别

首先我们要知道,中文输入的时候,尤其是在拼音输入的时候,显示在input里的并不是我们的汉字,而是占位的拼音和分隔符,这就意味着输入中文很可能包含了不止一个事件。再深入了解下,我们会发现:

  • 首先我们在键入拼音的时候,文本框会填入“虚拟文本”(待确认文本),这个时候会触发一个名为compositionstart的事件,然后input事件被触发,
  • 然后我们选中想要的文本的时候,虚拟文本被替换成真正的汉字,这个时候会触发另一个事件compositionend,并且重新触发input事件

看到这里我想大概就能解释为什么会生成两个tag了。我们在键入拼音的时候,input事件触发一次,截取一次内容,选中文本的时候input再触发一次,再截取一次内容,于是就生成了 2 个 tag。

解决方案

我们实际上想要的其实就是我们选中的文本,而不是占位的文本,既然如此,我们完全可以监听compositionstartcompositionend的这两个事件,我们只允许在后一个事件里进行截取添加 tag 的工作。

  • 通过监听 compositionstartcompositionend 事件,可以判断用户是否正在使用输入法输入内容。在 compositionstart 事件触发时,设置一个标志位(如 flag),表示开始输入;
  • compositionend 事件触发时,重置该标志位,表示输入结束。
  • 在 input 事件的处理函数中,检查这个标志位,如果正在输入(即标志位为 false),则不执行相关操作,从而避免在输入过程中多次触发 input 事件
  • 由于 input 事件可能在 compositionend 事件之前触发,可以通过设置一个延时器(如 setTimeout),在延时器的回调函数中检查标志位,如果标志位为 true(表示输入结束),则执行相关操作。这样可以确保在用户完成输入后,input 事件只被触发一次

好的理论成立,实践开始:

// 监听compositionstart事件
handleCompositionStart() {
  console.log('handleCompositionStart')
  // 表示用户正在输入
  this.isInput = true
},
// 监听compositionend事件
handleCompositionend(event) {
  console.log('handleCompositionend')
  // 输入结束,使用确认的文本
  this.isInput = false
  this.handleLabelsChange(event.data)
},
handleLabelsChange(value) {
      if (this.form.subjectiveLabels.length >= 5) {
        return
      }
      // 确认在键入的时候不进行截取
      if (!this.isInput) {
        setTimeout(() => {
          let labels = value + ''
          if (labels.length > 5) {
            labels = labels.slice(0, 5)
            this.form.subjectiveLabels.push(labels)
          }
        }, 0)
      }
    },

效果如何呢?

  • 输入英文或数字

image.png

  • 输入中文

image.png

如预期般运行。

总结

实际上中文键入包含了诸多事件,远不止这两个事件,还包括compositionupdate以及键盘的事件。如果你也遇到了类似的问题,不妨从这些事件中下手。