Front

Ant Design和CompositionEvent

中文输入法(包括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改变了全局变量的值再做后续处理。

avatar

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

  Subscribe  
提醒