ClickAwayListener在点击链接/按钮导航到其他路由时不会触发

7

我正在使用Material-UI ClickAwayListener组件,并且结合已有的react-router代码。按钮位于<ClickAwayListener> ... </ClickAwayListener>外面,因此我预期在导航到其他路由之前onClickAway会触发。但它没有触发。

以下是我的代码复制,以某种程度上展示我的意思

function Component(){

  const handleClickAway = () => {
    // Do something here
  }

  return (
  <>
    <button>
      <Link to="/other-route">Click here </Link>
    </button>
    // Some other element here
    <ClickAwayListener onClickAway={handleClickAway}>
      <div>
        // Content
      </div>
    </ClickAwayListener>
  </>
  )
}

如果我点击 <ClickAwayListener><button> 之外的任何位置,handleClickAway函数都会被触发。但是,如果我点击包含指向其他路由的链接的 <button> ,它就不会触发。

我尝试查看 ClickAwayListener 的源代码和这个部分,我相信它负责检测点击事件。

 React.useEffect(() => {
    if (mouseEvent !== false) {
      const mappedMouseEvent = mapEventPropToEvent(mouseEvent);
      const doc = ownerDocument(nodeRef.current);

      doc.addEventListener(mappedMouseEvent, handleClickAway);

      return () => {
        doc.removeEventListener(mappedMouseEvent, handleClickAway);
      };
    }

    return undefined;
  }, [handleClickAway, mouseEvent]);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.1/umd/react-dom.production.min.js"></script>

据我所理解,该部分首先会在组件挂载/重新渲染时添加一个点击事件监听器,并在组件卸载之前将其移除(useEffect()的默认行为)。但是,如果这是情况的话,在任何涉及单击 ClickAwayListener 区域之外的事件卸载组件之前,onClickAway 监听器应该被触发,因为该监听器仍附加到单击事件上。
简而言之,我期望的行为如下:
按钮单击 --> 触发onClickAway --> 组件卸载 --> 转到新路由 --> 运行useEffect()的清除代码 --> 删除监听器
但目前发生的情况是:
按钮单击 --> 组件卸载 --> 转到新路由 --> 运行useEffect()的清除代码 --> 删除监听器
请问为什么会出现这种情况?
5个回答

9
在 Tejogol 提供的 问题 中,一位评论者 建议在 ClickAwayListener 组件上使用这些属性,因为它允许组件在链接执行之前捕获触摸事件。
<ClickAwayListener mouseEvent="onMouseDown" touchEvent="onTouchStart">

我曾经遇到了和你一样的问题,而这个方法对我起了作用!而且这是最不会干扰的方法,因为我不想碰我的react-router-dom代码。


4

ClickAwayListener 组件通过将事件监听器附加到 document 上工作,当鼠标事件触发时,只有当鼠标事件不在元素内部时,才会触发 onClickAway

react-router-dom 中的 Link 组件本质上呈现出类似于这样的东西:

<a onClick={() => navigate()}>click</a>

当你点击链接并调用“navigate()”时,React会卸载当前组件并在下一帧中加载下一个页面组件。但问题是,“document”处理程序仅在下一次重新渲染后才被处理,在那时,来自“ClickAwayListener”的事件处理程序已被删除,因为它已被卸载,所以没有任何内容被调用。
可以通过等待下一次重新渲染后再处理来自“document”的处理程序的方式来解决该问题。
<button
  onClick={() => {
    setTimeout(() => {
      history.push("/2");
    });
  }}
>

演示

编辑 64531264/clickawaylistener-doesnt-fire-when-clicking-on-a-link-button-to-navigate-to-oth


为什么文档处理程序只会在下一次重新渲染后被处理?我认为它应该在导航之前被处理,因为它是本地事件处理程序。 - kunquan

1

2019年9月,与您问题相关的问题Material UI存储库中被开启,其中Clickawaylistener对于按钮和链接都不起作用。然而,截至2020年5月,该问题似乎还没有得到充分解决。我建议在存储库中开启一个新问题,以确认这不是与按钮相关的错误。

同时,作为一个潜在的解决方法,根据您的使用情况,您可以使用<Link><Button>组件并带有onClick属性:

import React from "react";
import ClickAwayListener from "@material-ui/core/ClickAwayListener";
import { Link, useHistory } from "react-router-dom";
import Button from "@material-ui/core/Button";

export default function Home() {
  let history = useHistory();

  const handleClickAway = () => {
    console.log("Clicked Away");
  };

  function handleClick() {
    handleClickAway();
    history.push("/about");
  }

  return (
    <div>
      <Link to="/about" onClick={handleClickAway}>
        Link
      </Link>
      <Button type="button" onClick={handleClick}>
        Button
      </Button>
      <ClickAwayListener onClickAway={handleClickAway}>
        <div>Home Page</div>
      </ClickAwayListener>
    </div>
  );
}

我还在Codesandbox上提供了一个可运行的示例。

Working Example


0

这里的大多数答案都提供了解决方案,但没有详细说明为什么会发生这种情况。所以我认为正在发生的事情是这样的:

当我发布这个问题时,我正在使用React 17。对于任何低于18的React版本,在使用addEventListener或任何异步操作,如async/awaitPromise时,都会出现一个“错误”。这在GitHub React 18 discussion中有讨论。

您可以在低于18的React版本中尝试此代码,您应该会观察到类似的结果。

import React, { useState, useEffect } from "react";

export default function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("Test in useEffect " + count);
  });

  useEffect(() => {
    console.log("Another one useEffect");
  });

  useEffect(() => {
    const button = document.getElementById("buttonId");
    button.addEventListener("click", handleClick);
    return () => {
      button.removeEventListener("click", handleClick);
    };
  }, []);

  const handleClick = () => {
    console.log("First print Count is " + count);

    setCount(count + 1);
    setCount(count + 100);
    console.log("Second print Count is " + count);
  };

  return (
    <>
      <div>Count is: {count} </div>
      <button id="buttonId">Click</button>
    </>
  );
}


0

我能够通过使用这个函数来解决这个问题,而不是使用 material-ui 的 ClickAwayListener:https://github.com/streamich/react-use/blob/master/src/useClickAway.ts

import { RefObject, useEffect, useRef } from 'react';
import { off, on } from './util';

const defaultEvents = ['mousedown', 'touchstart'];

const useClickAway = <E extends Event = Event>(
  ref: RefObject<HTMLElement | null>,
  onClickAway: (event: E) => void,
  events: string[] = defaultEvents
) => {
  const savedCallback = useRef(onClickAway);
  useEffect(() => {
    savedCallback.current = onClickAway;
  }, [onClickAway]);
  useEffect(() => {
    const handler = (event) => {
      const { current: el } = ref;
      el && !el.contains(event.target) && savedCallback.current(event);
    };
    for (const eventName of events) {
      on(document, eventName, handler);
    }
    return () => {
      for (const eventName of events) {
        off(document, eventName, handler);
      }
    };
  }, [events, ref]);
};

export default useClickAway;

我不想将该库作为依赖项导入,因此我将此代码创建为钩子,并将实用函数复制到其中

const on = (obj: any, ...args: any[]) => obj.addEventListener(...args);
const off = (obj: any, ...args: any[]) => obj.removeEventListener(...args);

这里提供了一个使用钩子的示例:https://streamich.github.io/react-use/?path=/story/ui-useclickaway--demo


这对我没有起作用。 - Joseph Castillo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接