产品经理:能不能把 Vue 的中文输入法 bug 解决了?

前言

有个挺常见的需求相信大家应该都遇到过,就是一个搜索框,边输入边提示,类似于下面这样:

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第1张图片

这玩意在前端也挺好实现的,就 v-model 然后 watch 再做个防抖请求接口呗!于是我:

<template>
  <input v-model="value">
template>

<script setup>
import { ref, watch } from 'vue';

const value = ref('')
watch(value, value => 请求接口(value))
script>

这里先省略掉防抖,因为希望代码看起来更简洁、更让人专注 bug 所在位置、减少干扰,大家能明白意思就行。为了更好的向大家展示这个 bug 我们把请求接口换成更加经典的 console.log,每打印一次就代表请求了一次接口:

<template>
  <input v-model="value">
template>

<script setup>
import { ref, watch } from 'vue';

const value = ref('')
watch(value, value => console.log(value))
script>

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第2张图片

只要输入框里的值发生变化了,那么就会请求接口,就会像下面这样:

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第3张图片

我们想搜索黑粉,结果刚打出第一个字母就已经开始替我们搜索了,随着字符越输越多,黑粉也慢慢呈现在了我们的面前。

中文输入法

但就在产品走查的时候,她们用中文输入法输入的时候却发现没有随着字符的输入而进行搜索:

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第4张图片

可以看到我们输入了这么多字符,但控制台从始至终都没打印过,证明了根本就没有监听到输入框里的变化。刚开始的时候我还狡辩呢,说什么像这种带着下划线的代表没输入完不会进入到输入里,所以输入框检测不到任何字符:

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第5张图片

她听完后半信半疑的走了,而我自己则是全信了,因为我以为事实就是这样。但过了一会她又回来了:你看百度谷歌它们的中文输入法都是没问题的啊:

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第6张图片

这下轮到我尴尬了:她会不会以为刚刚是我找了个蹩脚的理由敷衍她,但实际上我真这么认为的,因为我看了半天代码也没看出来哪写的不对,但她用事实告诉我这就是我的 bug。想了半天也没想出个所以然来,后来琢磨会不会是 Vuebug?因为无论百度谷歌还是必应它们都没用 Vue,所以它们没这个 bug,于是我用原生 JS 试了一下:



document.querySelector('input').oninput = ({
  target: { value }
}) => console.log(value)

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第7张图片

看来果然是 Vue 的锅,然后我赶忙跟她解释说这是 Vuebug,她一脸鄙视:

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第8张图片

你都赖多少次 Vue 了,是不是只要一有 bug 就都甩到 Vue 身上?
人家 Vue 好歹也是三大框架之一,不至于这么 Low 吧?一个带输入法的输入框都不支持?

我:这是真的啊,不过也不是不能解决,这次我把原生的写法写到 Vue 里去:

<template>
  <input ref="input">
template>

<script setup>
import { ref, onMounted } from 'vue';

const input = ref('');
onMounted(() => input.value.oninput = ({ target: { value } }) => console.log(value))
script>

<style>
html, body {
  background: #222;
}

input {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  margin: auto;
  width: 300px;
  height: 30px;
  padding: 0 10px;
  border: 1px solid #395e71;
  border-radius: 16px;
  color: antiquewhite;
  background: none;
  outline: none;
}
style>

结果居然是好的:

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第9张图片

接下来我陷入了短暂的沉思:原来的代码在刨除掉业务部分后在 上仅仅只用了 v-model 这一个属性,难道是 v-model 的问题?为了验证我的猜想我把原先的 v-model 给改成了 :value + @input

<template>
  <input :value="value" @input="value = $event.target.value">
template>

<script setup>
import { ref, watch } from 'vue';

const value = ref('')
watch(value, value => console.log(value))
script>

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第10张图片

这直接惊掉了我的下巴,因为在我印象中 v-model 就是 :value + @input 的语法糖,它俩是全等的关系:

 === 

因为 Vue 官网里就是这么写的:

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第11张图片

但为什么它们俩的行为不一致呢?而且为什么 v-model 会在中文输入法的情况下出 bug 呢?这些问题我们只能从源码里来找答案了。

源码

源码版本 3.3.4,在 packages 里的 runtime-dom 下的 src 内的 directives 中有个 vModel.ts

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第12张图片

在第 43 行有一个 vModelText,从名字上也可以看出来这是专门针对 的,因为只有这个元素是可以输入 text 的,其它以 vModel 开头的还有:

  • vModelCheckbox
  • vModelRadio
  • vModelSelect
  • vModelDynamic

但咱们想搞清楚的是中文输入法 bug,所以只看 vModelText 就行,其它的不用管。简单的扫了一眼,直觉告诉我问题出在了第 51 行:

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第13张图片

因为从命名上来看,.composing 是正在组合中的意思,我们用中文输入法打字的时候不就是正在组合中么:

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第14张图片

正在组合中就 return,后面的代码也就不执行了,这也非常符合我们目前所遇到的状况。不过 e.target 上有 .composing 这个属性么?我们用原生来试一下:

document.querySelector('input').oninput = ({
  target: { composing }
}) => console.log('composing: ', composing)

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第15张图片

可以看到根本就没这个属性,我就说嘛!我怎么不记得还有这个属性,那这个属性肯定是 Vue 自己添加上去的。我们继续来看源码,在第 6768 行我发现了这个:

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第16张图片

然后找到 onCompositionStartonCompositionEnd 这两个函数:

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第17张图片

原来还真是 Vue 自己加上去的属性,不过在这之前我从来没听说过 compositionstartcompositionend 这两个事件,盲猜肯定是在中文输入法(其实这么说不太准确,准确来说应该是所有需要把英文字母的组合转换成另外一种文字的输入法)输入的时候会触发的事件,我们来试一下:

document.querySelector('input').addEventListener('compositionstart', e => console.log(e))

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第18张图片

可以看到我们用英文打字时并没有打印出任何东西来,这代表了没有触发事件,当换成中文输入法时就能触发事件并打印出 CompositionEvent 了。原本我以为这是个 bug,但看完源码后我意识到这是有意为之,但我觉得这种有意为之并不好,因为官网上明明说了 v-model === :value + @input,不过在中文输入法下表现却并不一致。当然我知道这也是出于好意,尤雨溪可能觉得中文输入法打字时产生的字母并没有什么有效信息,比方说我们在搜索框里搜一个鼠头

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第19张图片

我们获取到的值是 shu tou,但这个 shu tou 并不是个没用的信息,用它照样可以显示出当前的热点搜索:

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第20张图片

然后随便一点击,就能看到一些:

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第21张图片

而且还有一个可能,就是本来就是想用英文搜,只不过是忘记了切换输入法:

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第22张图片

我们的产品就属于这种,我们做的是海外业务,用的是英文,但产品忘记了切换输入法,结果就出现了这个 bug

改进

他加上 .composing 的判断自然有他的道理,但大家仔细想想,如果有人遇到了和我相同的 bug,去看官网:

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第23张图片

那他会不会想到把 v-model 换成 :value + @input 就能够解决掉这个 bug?那不就是个简写么?意思不都是一样的么?谁能想得到啊家人们:

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第24张图片

但也不代表 .composing 没有意义,不过我觉得 comcomposing 的控制权应该交给开发者,v-model 不是有各种修饰符么:

产品经理:能不能把 Vue 的中文输入法 bug 解决了?_第25张图片

可以搞个内置修饰符 .composing

<input v-model.composing="value">

不过我觉得尤雨溪可能觉得这样写比较麻烦所以才做了 v-model 默认就有 .composing 的判断,那这样也可以搞个 .nocomposing 修饰符嘛:

<input v-model.nocomposing="value">

然后再在官网上标明 v-model 其实在中文输入法正在输入的情况下与 :value + @input 的表现并不一致,如果想要一致的话就加上 .nocomposing 修饰符:

<input v-model.nocomposing="value">

结语

大家可以去提个 PR,这可是一个成为 Vue Contributor 的好机会。如果尤雨溪不明白为什么要这么改时,你可以把我这篇文章的链接发给他看,相信他就明白了 v-model 有可能会给大家带来的困境了。

你可能感兴趣的:(vue.js,前端,javascript)