最近在 react
使用中遇到了一个问题,react
事件池的问题,react
文档是这样解释的:
SyntheticEvent
是合并而来。这意味着SyntheticEvent
对象可能会被重用,而且在事件回调函数被调用后,所有的属性都会无效。出于性能考虑,你不能通过异步访问事件。
如果你想异步访问事件属性,你需在事件上调用
event.persist()
,此方法会从池中移除合成事件,允许用户代码保留对事件的引用。
具体表现可以看下下面这一段代码
function onClick(event) {
console.log(event); // => nullified object.
console.log(event.type); // => "click"
const eventType = event.type; // => "click"
// 异步访问
setTimeout(function() {
console.log(event.type); // 报错
console.log(eventType); // => "click"
}, 0);
// setState是异步的 报错
this.setState({ clickEvent: event });
// 正常
this.setState({ eventType });
}
由此可见,下面这段代码输入框输入的时候绝对会报错
import React, { useState } from 'react';
import { debounce } from '../../util';
export default () => {
const [span, setSpan] = useState('');
const onChange = debounce((e: React.SyntheticEvent) => {
const val = (e.target as HTMLInputElement).value;
setSpan(val);
}, 300);
return (
<div>
<input type="text" onChange={onChange} />
<span>{span}</span>
</div>
);
};
以下是几种解决方案
回调函数参数处理
对 onChange
的回调参数做一下处理,直接传递 value
,代码如下:
import React, { useState } from 'react';
import { debounce } from '../../util';
export default () => {
const [span, setSpan] = useState('');
const onChange = debounce((val: string) => {
setSpan(val);
}, 300);
return (
<div>
<input
type="text"
onChange={(e: React.SyntheticEvent) => onChange((e.target as HTMLInputElement).value)}
/>
<span>{span}</span>
</div>
);
};
修改一下可以改成这样
import React, { useState } from 'react';
import { debounce } from '../../util';
export default () => {
const [span, setSpan] = useState('');
const debounceFn = debounce((val: string) => {
setSpan(val);
}, 300);
const onChange = (e: React.SyntheticEvent) => {
const val = (e.target as HTMLInputElement).value;
debounceFn(val);
};
return (
<div>
<input type="text" onChange={onChange} />
<span>{span}</span>
</div>
);
};
用 hook
去处理
既然都用到了 react hook
为什么不尝试下用自定义的 hook
来处理,自定义一个 useDebounce
的 hook
来处理:
import { useState, useEffect } from 'react';
export default function useDebounce<T>(value: T, delay: number = 300): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
然后再组件中这样使用:
import React, { useState } from 'react';
import useDebounce from '../../util/hooks/debounce';
export default () => {
const [span, setSpan] = useState('');
const debounceSpan = useDebounce(span);
const onChange = (e: React.SyntheticEvent) => {
const val = (e.target as HTMLInputElement).value;
setSpan(val);
};
return (
<div>
<input type="text" onChange={onChange} />
<span>{debounceSpan}</span>
</div>
);
};
useDebounce
的原理和防抖动函数实现的原理是一样的。