setTimeout回调中evt.target.value取值为空的原因解答
问题产生原因
这个现象是React合成事件的事件池复用机制导致的:
- React没有直接把浏览器原生事件对象传给JSX绑定的事件回调,而是自己封装了一套跨浏览器兼容的
SyntheticEvent(合成事件)对象。为了减少频繁创建销毁事件对象的性能开销,React维护了一个事件对象池:每次事件触发时从池中取出一个合成事件实例填充属性传给回调,等回调同步执行完成后,会立刻清空这个实例上的所有属性,再放回池中等待下次复用。 - 第一段代码里的
setTimeout回调是异步执行的,它的执行时机晚于当前事件处理函数的同步执行阶段。等定时器回调跑起来读evt.target.value的时候,对应的合成事件对象早就被React回收重置了,所有属性都被清空,自然读不到正确的输入值。 - 第二段代码提前在事件回调的同步执行阶段,把
evt.target.value赋值给了普通变量term。字符串是JavaScript的基本数据类型,赋值时会拷贝原始值,不会持有对事件对象属性的引用,后续合成事件被回收清空的操作完全不会影响这个已经存下来的独立值,所以定时器里能正常拿到正确的输入内容。
补充说明
如果不想提前缓存值,在React16及更早版本里可以调用evt.persist()方法,告诉React不要回收当前这个合成事件对象,后续异步代码里就能正常读取事件属性,写法如下:
const handleInputChange = (evt) => { evt.preventDefault(); evt.persist(); // 移除事件对象的池化回收逻辑 setTimeout(() => {console.log(evt.target.value)}, 500); }
React 17及之后的版本已经移除了合成事件的事件池机制,不会再出现这类异步读事件属性为空的问题,但提前在同步阶段取需要的值依然是更通用、兼容性更好的写法。
内容的提问来源于stack exchange,提问作者nkhaitov
相关产品推荐
相关产品推荐

