最近接触umi,在使用umi搭建后台项目时发现,有一个需求是在input框输入时进行防抖查询。但是Antd的input框,onChange事件触发返回的callback如果用防抖函数包裹的话,event对象的target属性是null。
效果如下:
<Input
placeholder="任务ID"
onChange={debounce(e => {
console.log(e)
if (e.target) {
let q = e.target.value;
setPendingId(q || undefined)
}
}, 500)}
allowClear
/>
打印结果:

很明显target是null,为什么正常callback可以获取到,使用debouce函数就出现如此怪异的现象?
查阅一番之后得知,原来是React的合成事件(SyntheticEvent)导致的。
合成事件(SyntheticEvent)
事件处理程序通过 合成事件(SyntheticEvent)的实例传递,SyntheticEvent 是浏览器原生事件跨浏览器的封装。SyntheticEvent 和浏览器原生事件一样有 stopPropagation()、preventDefault() 接口,而且这些接口夸浏览器兼容。
事件池(Event Pooling)
SyntheticEvent 是池化的. 这意味着 SyntheticEvent 对象将会被重用,并且所有的属性都会在事件回调被调用后被 nullified。 这是因为性能的原因。 因此,你不能异步的访问事件。
通过了解事件系统,也就不难理解为什么会报错了。因为经过 debounce 包装后的回调函数,变成了一个异步事件,在池化后被 nullified 了。
那么怎样才能解决这个问题?
- 对于class组件:
通过在回调事件顶部加上 e.persist() 就可以从池中移除合成事件,并允许对事件的引用保留。并且把需要异步执行的回调函数抽离出来封装,并且在组件初始化话的时候就将其 debounce 化,就可以得到我们想要的效果。
import react, { Component } from 'react';
import { debounce } from 'lodash.debounce';
export default class Debounce extends Component {
construtor() {
super();
this.callAjax = debounce(this.callAjax, 300);
}
callAjax = (value) => {
console.log('value :: ', value);
// call ajax
}
printChange(e) {
e.persist();
this.callAjax(e.target.value);
}
render() {
return (
<div>
<input onChange={this.printChange} />
</div>
);
}
}
- 对于Function组件
因为我使用的是Hooks,所以换了个想法,使用ref,直接上代码
// 使用iseRef
import React, { useState, useEffect, useRef } from 'react';
const fileInputEl: any = useRef(null)
const callAjax = (value) => {
setPendingId(value || undefined);
}
let printChange = debounce((e) => {
e.persist()
console.log(fileInputEl);
callAjax(fileInputEl.current.input.state.value)
}, 500)
<Input
placeholder="任务ID"
ref={fileInputEl}
onChange={printChange}
allowClear
/>
打印结果

后面发现只要不直接用debounce包裹回调函数,使用函数调用传参的方法就可以拿到e.target了。
const printChange = (e) => {
taskChange(e.target.value)
}
const taskChange = debounce((value) => {
setPendingId(value || undefined);
}, 500)
<Input
placeholder="任务ID"
ref={fileInputEl}
onChange={printChange}
allowClear
/>
这里就可以拿到input中的value了,哈哈,觉得有用的话就给个赞吧。