产生的背景:
我接到了一个需求,要求在一个表单里输入标签,每个标签不超过 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,结果正常出现内容:
- 现在让我们说中文,输入
我是火车王,结果如下:
什么情况?直接出现了 2 条数据!
但是英文字符和数字明显不会有这个问题,那么问题就一定出在了中文这里。
那么输入英文的时候和输入中文到底有什么区别呢?
英文输入法和中文输入法的区别
首先我们要知道,中文输入的时候,尤其是在拼音输入的时候,显示在input里的并不是我们的汉字,而是占位的拼音和分隔符,这就意味着输入中文很可能包含了不止一个事件。再深入了解下,我们会发现:
- 首先我们在键入拼音的时候,文本框会填入“虚拟文本”(待确认文本),这个时候会触发一个名为
compositionstart的事件,然后input事件被触发, - 然后我们选中想要的文本的时候,虚拟文本被替换成真正的汉字,这个时候会触发另一个事件
compositionend,并且重新触发input事件
看到这里我想大概就能解释为什么会生成两个tag了。我们在键入拼音的时候,input事件触发一次,截取一次内容,选中文本的时候input再触发一次,再截取一次内容,于是就生成了 2 个 tag。
解决方案
我们实际上想要的其实就是我们选中的文本,而不是占位的文本,既然如此,我们完全可以监听compositionstart与compositionend的这两个事件,我们只允许在后一个事件里进行截取添加 tag 的工作。
- 通过监听
compositionstart和compositionend事件,可以判断用户是否正在使用输入法输入内容。在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)
}
},
效果如何呢?
- 输入英文或数字
- 输入中文
如预期般运行。
总结
实际上中文键入包含了诸多事件,远不止这两个事件,还包括compositionupdate以及键盘的事件。如果你也遇到了类似的问题,不妨从这些事件中下手。