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 钩子的明智使用。