我正在寻找一种方法来检测单击事件是否发生在组件之外,如本文所述。jQueryclosest()用于查看单击事件的目标是否将dom元素作为其父元素之一。如果存在匹配项,则单击事件属于其中一个子项,因此不被视为在组件之外。

因此,在我的组件中,我想将一个单击处理程序附加到窗口。当处理程序启动时,我需要将目标与组件的dom子级进行比较。

click事件包含类似“path”的财产,它似乎保存了事件经过的dom路径。我不知道该比较什么,或者如何最好地遍历它,我想肯定有人已经把它放在了一个聪明的效用函数中。。。不


当前回答

带打字稿函数Tooltip():ReactElement{const[show,setShow]=useState(false);const-ref=useRef<HTMLDivElement>(null);使用效果(()=>{函数handleClickOutside(事件:MouseEvent):void{if(ref.current&&!ref.current.includes(event.target作为节点)){setShow(false);}}//绑定事件侦听器document.addEventListener('mouseown',handleClickOutside);返回()=>{//清理时取消绑定事件侦听器document.removeEventListener('usedown',handleClickOutside);};});返回(<div ref={ref}></div>) }

其他回答

在这里尝试了许多方法之后,我决定使用github.com/Pomax/react-onclickoutside,因为它非常完整。

我通过npm安装了模块并将其导入到组件中:

import onClickOutside from 'react-onclickoutside'

然后,在组件类中,我定义了handleClickOutside方法:

handleClickOutside = () => {
  console.log('onClickOutside() method called')
}

导出组件时,我将其包装在onClickOutside()中:

export default onClickOutside(NameOfComponent)

就是这样。

如果您需要typescript版本:

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

interface Props {
  ref: React.MutableRefObject<any>;

}

export const useOutsideAlerter = ({ ref }: Props) => {
  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      if (ref.current && !ref.current.contains(event.target as Node)) {
       //do what ever you want
      }
    };
    // Bind the event listener
    document.addEventListener("mousedown", handleClickOutside);
    return () => {
      // Unbind the event listener on clean up
      document.removeEventListener("mousedown", handleClickOutside);
    };
  }, [ref]);
};
export default useOutsideAlerter;

如果您想扩展它以关闭模态或隐藏某些内容,也可以执行以下操作:

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

interface Props {
  ref: React.MutableRefObject<any>;
  setter: React.Dispatch<React.SetStateAction<boolean>>;
}

export const useOutsideAlerter = ({ ref, setter }: Props) => {
  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      if (ref.current && !ref.current.contains(event.target as Node)) {
        setter(false);
      }
    };
    // Bind the event listener
    document.addEventListener("mousedown", handleClickOutside);
    return () => {
      // Unbind the event listener on clean up
      document.removeEventListener("mousedown", handleClickOutside);
    };
  }, [ref, setter]);
};
export default useOutsideAlerter;

这是最适合我制作下拉菜单的方法:

handleClick = () => {
    document.getElementById("myDrop").classList.toggle("showing");
}

render() {

    return (
        <div className="courses">
            <div class="dropdownBody">
                <button onClick={this.handleClick} onBlur={this.handleClick} class="dropbtn">Dropdown</button>
                <div id="myDrop" class="dropdown-content">
                    <a href="#home">Home</a>
                    <a href="#about">About</a>
                    <a href="#contact">Contact</a>
                </div>
            </div>
        </div>
    )
}

我有一个需要有条件地将孩子插入模态的例子。像这样,贝娄。

const [view, setView] = useState(VIEWS.SomeView)

return (
    <Modal onClose={onClose}>
      {VIEWS.Result === view ? (
        <Result onDeny={() => setView(VIEWS.Details)} />
      ) : VIEWS.Details === view ? (
        <Details onDeny={() => setView(VIEWS.Result) /> />
      ) : null}
    </Modal>
  )

所以parent.contains(event.target)在这里不起作用,因为一旦分离了子级,parent(modal)就不再包含event.targe。

我的解决方案(到目前为止有效,没有任何问题)是这样写:

const listener = (event: MouseEvent) => {
   if (parentNodeRef && !event.path.includes(parentNodeRef)) callback()
}

若父级包含已经分离的树中的元素,那个么它不会触发回调。

编辑:event.path是新的,尚未在所有浏览器中退出。请改用composedPath。

因为对我来说!ref.current.contains(e.target)无法工作,因为ref中包含的DOM元素正在更改,我提出了一个稍微不同的解决方案:

function useClickOutside<T extends HTMLElement>(
  element: T | null,
  onClickOutside: () => void,
) {
  useEffect(() => {
    function handleClickOutside(event: MouseEvent) {
      const xCoord = event.clientX;
      const yCoord = event.clientY;

      if (element) {
        const { right, x, bottom, y } = element.getBoundingClientRect();
        if (xCoord < right && xCoord > x && yCoord < bottom && yCoord > y) {
          return;
        }

        onClickOutside();
      }
    }

    document.addEventListener('click', handleClickOutside);
    return () => {
      document.removeEventListener('click', handleClickOutside);
    };
  }, [element, onClickOutside]);