记录于 07月21日 · 火曜日4 min read
中文输入法(包括 CJK 输入法)都有的问题:如果<input>
上监听input 事件,在 Win/Mac 上每次按键都会触发。例如用拼音输入法输入动漫驿站,用户可能按键 d.o.n.g.m.a…会触发 13 次事件,但其实只需要触发一次处理输入后的结果。早年间有很多 trick 的做法,比如 keyup 判断输入框内容是否是中文。现在是可以放心使用composition 事件了。
场景是:使用Ant Design 的 Select 搜索框,监听 composition,输入结束后再搜索。
因为只是输入的判断,使用 state/hook 反而复杂了,用一个全局变量标记是否正在输入,compositionstart 为 true,compositionend 赋值 false,在获取数据前检查这个标记,是 false 才获取,主要代码如下:
window.isInputing = false;
const Form = (props) => {
const compositionListener = (e) => {
window.isInputing = e.type === 'compositionstart';
};
const fetchData = (v) => {
if (window.isInputing === false) {
fetch();
}
};
return (
<Select
showSearch
value={v}
onSearch={v => fetchData(v)}
/>
);
}
JavaScript
如果是普通<input>
可以直接addEventListener,但这里的<Select>
是组件,而且没有onChange/onInput。从onFocus也获取不到实际的元素,它是一个setTimeout。所以按照文档描述现成的只能用onInputKeyDown了,修改<Select>
:
return (
<Select
showSearch
value={v}
onSearch={v => fetchData(v)}
onInputKeyDown={(e) => {
const el = e.nativeEvent.target;
el.addEventListener('compositionstart', compositionListener);
el.addEventListener('compositionend', compositionListener);
}}
/>
);
}
JavaScript
这么写就冒出几种可能性:
- 如果按照之前的写法,compositionListener函数在组件内,浏览器认为是不同的事件,会被重复绑定。要手动添加一个标记,比如修改元素的dataset,避免重复绑定。
- 把compositionListener函数放在组件外,只会被绑定一次,但要调用组件内部的函数——比如fetchData——就变复杂了。
- 绑定Composition事件后,在Chrome浏览器也是先触发onChange,才到compositionend。也就是在改变全局变量标记前已经执行了onSearch。
暂时不考虑把fetchData写成hook的方案,只在现有代码基础上改动,一时想不出最优写法,先改成了如果是Chrome浏览器执行一次fetchData。
const compositionListener = (e) => {
window.isInputing = e.type === 'compositionstart';
if (window.chrome) {
fetchData(e.target.value);
}
};
JavaScript
总结来看相对麻烦的还是CompositionEvent,都2020年了问题仍然存在。以前用setTimeout延迟100ms左右,等compositionend改变了全局变量的值再做后续处理。