addEventListener inside useLayoutEffect: 多次添加事件监听器
addEventListener inside useLayoutEffect: the event listener is added multiple times
我有这个 useLayoutEffect
(它在 useEffect
中发生相同,它基本上监听 touchStart 和 touchEnd 的元素以了解用户试图滑动的方向。
useLayoutEffect(() => {
if (window) {
setWindowWidth(window.innerWidth);
window.onresize = () => {
setWindowWidth(window.innerWidth);
};
}
let touchStartX = 0;
let touchEndX = 0;
let movingTestimonials = false;
const handleGesture = () => {
if (movingTestimonials) {
return false;
}
movingTestimonials = true;
const diff = touchStartX - touchEndX;
const moves = diff > 0 ? 1 : -1;
const next = (isActive + moves) % Math.ceil(data.testimonials.length / testimonialsToShow());
const finalNext = next >= 0 ? next : testimonialsToShow() + 1 - next;
setIsActive(finalNext);
movingTestimonials = false;
};
const touchStartHandler = (e) => {
touchStartX = e.touches[0]?.pageX;
};
const touchEndtHandler = (e) => {
touchEndX = e.changedTouches[0]?.pageX;
if (touchEndX) {
handleGesture();
}
};
if (testimonialRef && testimonialRef.current) {
testimonialRef.current.addEventListener('touchstart', touchStartHandler, true);
testimonialRef.current.addEventListener('touchend', touchEndtHandler, true);
console.log('added event listeners');
}
}, [isActive]);
在用户第一次滑动时,一切都按预期工作,然后它变得非常慢,因为事件侦听器以指数方式添加。
https://watch.screencastify.com/v/ltYIkI9X8uSlJPm7hLXG
我怎样才能做到只添加一次 eventListener?
您不能使用它当前的实现方式,因为 handleGesture
依赖于 isActive
,因此每次更改时都必须重新创建事件侦听器以获取当前值。您可以进行事件清理以确保在任何时候只附加一组正确的处理程序:
useLayoutEffect(() => {
...
return () => {
testimonialRef.current.removeEventListener('touchstart', touchStartHandler);
testimonialRef.current.removeEventListener('touchend', touchEndtHandler);
}
}, [isActive]);
或者,您可以摆脱依赖关系,只使用局部范围的变量跟踪更改。这只有在 testimonalRef
被分配到挂载上并且不会改变时才有效:
useLayoutEffect(() => {
...
let touchStartX = 0;
let touchEndX = 0;
let _isActive = false;
let movingTestimonials = false;
const handleGesture = () => {
if (movingTestimonials) {
return false;
}
movingTestimonials = true;
const diff = touchStartX - touchEndX;
const moves = diff > 0 ? 1 : -1;
const next = (_isActive + moves) % Math.ceil(data.testimonials.length / testimonialsToShow());
const finalNext = next >= 0 ? next : testimonialsToShow() + 1 - next;
_isActive = finalNext;
setIsActive(finalNext);
movingTestimonials = false;
};
...
}, []);
老实说,在 React 环境中,您几乎不需要手动将事件侦听器附加到 DOM 元素。当你真的应该尝试通过 JSX 附加你的事件处理程序时,你在这里与 React 作斗争——这不是 useLayoutEffect
钩子的明智使用。
我有这个 useLayoutEffect
(它在 useEffect
中发生相同,它基本上监听 touchStart 和 touchEnd 的元素以了解用户试图滑动的方向。
useLayoutEffect(() => {
if (window) {
setWindowWidth(window.innerWidth);
window.onresize = () => {
setWindowWidth(window.innerWidth);
};
}
let touchStartX = 0;
let touchEndX = 0;
let movingTestimonials = false;
const handleGesture = () => {
if (movingTestimonials) {
return false;
}
movingTestimonials = true;
const diff = touchStartX - touchEndX;
const moves = diff > 0 ? 1 : -1;
const next = (isActive + moves) % Math.ceil(data.testimonials.length / testimonialsToShow());
const finalNext = next >= 0 ? next : testimonialsToShow() + 1 - next;
setIsActive(finalNext);
movingTestimonials = false;
};
const touchStartHandler = (e) => {
touchStartX = e.touches[0]?.pageX;
};
const touchEndtHandler = (e) => {
touchEndX = e.changedTouches[0]?.pageX;
if (touchEndX) {
handleGesture();
}
};
if (testimonialRef && testimonialRef.current) {
testimonialRef.current.addEventListener('touchstart', touchStartHandler, true);
testimonialRef.current.addEventListener('touchend', touchEndtHandler, true);
console.log('added event listeners');
}
}, [isActive]);
在用户第一次滑动时,一切都按预期工作,然后它变得非常慢,因为事件侦听器以指数方式添加。
https://watch.screencastify.com/v/ltYIkI9X8uSlJPm7hLXG
我怎样才能做到只添加一次 eventListener?
您不能使用它当前的实现方式,因为 handleGesture
依赖于 isActive
,因此每次更改时都必须重新创建事件侦听器以获取当前值。您可以进行事件清理以确保在任何时候只附加一组正确的处理程序:
useLayoutEffect(() => {
...
return () => {
testimonialRef.current.removeEventListener('touchstart', touchStartHandler);
testimonialRef.current.removeEventListener('touchend', touchEndtHandler);
}
}, [isActive]);
或者,您可以摆脱依赖关系,只使用局部范围的变量跟踪更改。这只有在 testimonalRef
被分配到挂载上并且不会改变时才有效:
useLayoutEffect(() => {
...
let touchStartX = 0;
let touchEndX = 0;
let _isActive = false;
let movingTestimonials = false;
const handleGesture = () => {
if (movingTestimonials) {
return false;
}
movingTestimonials = true;
const diff = touchStartX - touchEndX;
const moves = diff > 0 ? 1 : -1;
const next = (_isActive + moves) % Math.ceil(data.testimonials.length / testimonialsToShow());
const finalNext = next >= 0 ? next : testimonialsToShow() + 1 - next;
_isActive = finalNext;
setIsActive(finalNext);
movingTestimonials = false;
};
...
}, []);
老实说,在 React 环境中,您几乎不需要手动将事件侦听器附加到 DOM 元素。当你真的应该尝试通过 JSX 附加你的事件处理程序时,你在这里与 React 作斗争——这不是 useLayoutEffect
钩子的明智使用。