antd的input框onChange事件使用防抖无法获取到e.target

9,205 阅读2分钟

最近接触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 了。

那么怎样才能解决这个问题?

  1. 对于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>
    );
  }
}
  1. 对于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了,哈哈,觉得有用的话就给个赞吧。