前段时间被这样一个bug给折磨到,在ios手机端,切换中文输入法输入打拼音到input输入框的时候,发现打拼音的时候也会频繁触发input事件以至于输入框一直处于抖动状态,加上防抖截流也无济于事反而会让用户觉得特别的卡顿。
问题排查:
1.公司项目用的是taro进行开发,所以是taro的问题吗?
查看了源码,发现taro只是做了一层套娃,底层是react框架
2.那react框架问题吗?
写了个react的demo发现,react其实也有这样的问题
const Demo=()=>{
const [value,setValue]=useState("");
return (
<>
<input onInput={(e)=>{
console.log("value:",e.target.value)
setValue(e.target.value)
}}/>
<span>{value}</span>
</>
)
}
看过react的源码的人都会知道,react对于原生dom事件其实也是没有过多的去处理,于是我做了一下论证
3.是不是元素dom事件就是这样的呢?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<input type="text" >
<script>
const input=document.querySelector('input')
input.addEventListener('input',function(e){
console.log("e",e)
})
</script>
</body>
</html>
验证结果如下确实如此
那么接下来就轮到主角上场了
compositionstart
文本合成系统如 input method editor(即输入法编辑器)开始新的输入合成时会触发 compositionstart
事件。
例如,当用户使用拼音输入法开始输入汉字时,这个事件就会被触发。
compositionend
当文本段落的组成完成或取消时,compositionend 事件将被触发 (具有特殊字符的触发,需要一系列键和其他输入,如语音识别或移动中的字词建议)。
于是我对react代码的input输入框添加了以上两方法
const Demo = () => {
const [value, setValue] = useState("");
return (
<>
<input
onCompositionStart={(e) => {
console.log("onCompositionStart")
}}
onInput={(e) => {
console.log("value:", e.target.value);
setValue(e.target.value);
}}
/>
<span>{value}</span>
</>
);
};
当切换到中文输入到时候 compositionstart事件触发了
当输入完成的时候compositionend触发了
当切换会英文输入的时候,就不会触发
于是乎对代码做了这样的改造:
const Demo = () => {
const [value, setValue] = useState("");
return (
<>
<input
onCompositionStart={(e) => {
console.log("onCompositionStart")
e.target.composing = true
}}
onCompositionEnd={(e) => {
console.log("onCompositionEnd",e.target.value)
if(!e.target.composing)return;
(e.target.composing = false)
setValue(e.target.value);
}}
onInput={(e) => {
if(e.target.composing) return;
console.log("value:", e.target.value);
setValue(e.target.value);
}}
/>
<span>{value}</span>
</>
);
};
也就完全解决了中文输入的时候输入框问题
以上解决思路其实来源于vue源码v-model的指令实现的时候对输入框事件做了的处理
const directive = {
inserted (el, binding, vnode, oldVnode) {
if (vnode.tag === 'select') {
// #6903
if (oldVnode.elm && !oldVnode.elm._vOptions) {
mergeVNodeHook(vnode, 'postpatch', () => {
directive.componentUpdated(el, binding, vnode)
})
} else {
setSelected(el, binding, vnode.context)
}
el._vOptions = [].map.call(el.options, getValue)
} else if (vnode.tag === 'textarea' || isTextInputType(el.type)) {
el._vModifiers = binding.modifiers
if (!binding.modifiers.lazy) {
el.addEventListener('compositionstart', onCompositionStart)
el.addEventListener('compositionend', onCompositionEnd)
// Safari < 10.2 & UIWebView doesn't fire compositionend when
// switching focus before confirming composition choice
// this also fixes the issue where some browsers e.g. iOS Chrome
// fires "change" instead of "input" on autocomplete.
el.addEventListener('change', onCompositionEnd)
/* istanbul ignore if */
if (isIE9) {
el.vmodel = true
}
}
}
},
function onCompositionStart (e) {
e.target.composing = true
}
function onCompositionEnd (e) {
// prevent triggering an input event for no reason
if (!e.target.composing) return
e.target.composing = false
trigger(e.target, 'input')
}
function trigger (el, type) {
const e = document.createEvent('HTMLEvents')
e.initEvent(type, true, true)
el.dispatchEvent(e)
}
由此看出鱿大大,在写vue框架的时候是多么细心,这些细节都考虑到了