起因
在实践中遇到一个场景,是在useEffect
中注册一个事件监听函数,在事件监听的回调函数中,将接收到的数据设置到数组类型的state
中,同时提供一个boolean
类型的state
与页面中的按钮绑定,控制接收和暂停,当设置为暂停时,回调函数中就不更新state
,示意如下:
function MyComponent() {
const [datas, setDatas] = useState<number>([]); // 展示数据
const [paused, setPause] = useState(false); // 控制是否接受新数据
useEffect(() => {
const callback = (data: number) => {
if (!pasued) {
// 将接收的数据加入到state里
setDatas(oldDatas => [...oldDatas, data]);
}
}
// GlobalEvent是封装的一个事件,注册一个监听器来接收数据
GlobalEvent.addMessageListener("eventId", callback);
return () => {
GlobalEvent.removeMessageListener("eventId", callback);
}
}, []);
return (
<div>
<Button onClick={() => setPause(state => !state)}>{paused ? "继续" : "暂停"}</Button>
<div>
);
}
这样,直观上开起来没有问题,通过设置pasued
的状态来动态控制数据的设置,但是实际运行起来会发现paused
并没有起到任何作用,原因是回调函数在此处表现上是一个闭包,其里边拿到的paused
变量的地址是paused
初始值的地址,当paused
的值发生变化时,外部的paused
变量指向了新的内存地址,但回调函数内部依旧保持的旧的引用,因此外状态部改变,内部不会生效。
解决
既然是因为变量引用无法更新,那么就可以通过ref
来引用,将回调函数与ref
相关联,将paused
的状态通过ref
引用进行传递,当pasued
的状态发生变化时,及时更新ref
的引用即可,如下:
function MyComponent() {
const [datas, setDatas] = useState<number>([]); // 展示数据
const [paused, setPause] = useState(false); // 控制是否接受新数据
const pausedRef = useRef<boolean>(); // paused的动态引用
const callback = useCallback((data: number) => {
// 通过ref动态引用获取到paused的当前状态
if (!pausedRef.current) {
setDatas(oldDatas => [...oldDatas, data]);
}
}, [pausedRef]);
useEffect(() => {
// GlobalEvent是封装的一个事件
GlobalEvent.addMessageListener("eventId", callback);
return () => {
GlobalEvent.removeMessageListener("eventId", callback);
}
}, []);
useEffect(() => {
// 当paused的值发生变化时更新ref的引用
pausedRef.currect = pause;
}, [paused]);
return (
<div>
<Button onClick={() => setPause(state => !state)}>{paused ? "继续" : "暂停"}</Button>
<div>
);
}