中文输入法(包括CJK输入法)都有的问题:如果<input>上监听input事件,在Win/Mac上每次按键都会触发。例如用拼音输入法输入动漫驿站,用户可能按键d.o.n.g.m.a…会触发13次事件,但其实只需要触发一次处理输入后的结果。早年间有很多trick的做法,比如keyup判断输入框内容是否是中文。现在是可以放心使用composition事件了。
场景是:使用Ant Design的Select搜索框,监听composition,输入结束后再搜索。
macOS 10.15.2
Chrome 79
Ant Design 3.25.2
因为只是输入的判断,使用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)}
/>
);
}
如果是普通<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);
}}
/>
);
}
这么写就冒出几种可能性:
- 如果按照之前的写法,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);
}
};
总结来看相对麻烦的还是CompositionEvent,都2020年了问题仍然存在。以前用setTimeout延迟100ms左右,等compositionend改变了全局变量的值再做后续处理。