记一次 react 防抖导致无法获取输入框内容问题

912 阅读2分钟

事情是这样的

我在 react 组件中尝试给 input 的 onChange 事件加上防抖处理后,发现拿不到输入的内容,打开控制台,发现有如下的错误,原来是拿到的 target 对象为 null:

const handleChange = debounce((e) => {
    const searchKey = e.target.value;
    // 请求与搜索关键词相关的数据
    handleSearch(searchKey);
}, 300);

<input onChange={handleChange}>

除此之外还有一个错误似乎解释了为什么 target 为 null,意思大概是:

出于性能的考虑,合成事件会被复用,如果你看到这个提示,说明你正尝试访问一个被重置了的合成事件中的 target,而这个 target 被设置成了 null。如果你想要保持原始的合成事件,可以使用 event.persist()

为什么会出现

什么是合成事件?

React 为了抹平不同浏览器事件对象的差异,引入了合成事件,合成事件是 React 用来模拟原生事件所有能力的一个对象,它拥有和浏览器原生事件相同的接口。

什么是事件池?

React 使用事件池统一管理合成事件,可以在事件池中获取或者释放事件对象,来避免频繁的创建或回收事件对象。React 官方文档中有提到「当所有事件处理函数被调用之后,其所有属性都会被置空」,还举了以下例子:

function handleChange(e) {
  // 这并不会输出我们想要的结果,因为事件对象会被重复使用,先前的对象属性在函数执行完后被置为了空
  setTimeout(() => {
    console.log(e.target.value); // error: e.target 为 null
  }, 100);
}

看到这里,我们也就大概明白了,为什么在对 onChange 事件加入了 debounce 操作后就取不到输入框中的内容了,是由于合成事件对象中的属性被重置,导致 target 为 null。

该如何解决

  1. 使用 e.persist()

使用 e.persist() 可以保持此合成事件在事件处理函数被调用之后不对属性进行重置的操作,需要注意的是在 React 17 版本将不再使用事件池,也就不会存在此问题了。

  1. 将输入框改为非受控组件

这里我们想要的是拿到输入框中的内容,那么我们可以通过 ref 拿到 DOM 节点中的值

this.inputRef = React.createRef();

const handleChange = debounce(() => {
    const searchKey = this.inputRef.current.value;
    // 请求与搜索关键词相关的数据
    handleSearch(searchKey);
}, 300);

<input ref={this.inputRef} onChange={handleChange}>
  1. 修改 debounce 对象

我们这里对 onChange 事件进行 debounce 操作,是因为我们不想频繁的发起搜索的动作,那么这里我们可以改为对搜索动作进行 debounce

const handleSearchKeyChange = debounce((searchKey: string) => {
    // 请求与搜索关键词相关的数据
    handleSearch(searchKey);
}, 300);

const handleChange = (e) => {
    const searchKey = e.target.value;
    // 请求与搜索关键词相关的数据
    handleSearchKeyChange(searchKey);
};

<input onChange={handleChange}>

参考资料