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

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

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


当前回答

只需使用mui(material ui)中的ClickAwayListener:

<ClickAwayListener onClickAway={handleClickAway}>
    {children}
<ClickAwayListener >

有关更多信息,请查看:https://mui.com/base/react-click-away-listener/

其他回答

使用OnClickOutside Hook-反应16.8+

创建通用useOnOutsideClick函数

export const useOnOutsideClick = handleOutsideClick => {
  const innerBorderRef = useRef();

  const onClick = event => {
    if (
      innerBorderRef.current &&
      !innerBorderRef.current.contains(event.target)
    ) {
      handleOutsideClick();
    }
  };

  useMountEffect(() => {
    document.addEventListener("click", onClick, true);
    return () => {
      document.removeEventListener("click", onClick, true);
    };
  });

  return { innerBorderRef };
};

const useMountEffect = fun => useEffect(fun, []);

然后在任何功能组件中使用钩子。

const OutsideClickDemo = ({ currentMode, changeContactAppMode }) => {

  const [open, setOpen] = useState(false);
  const { innerBorderRef } = useOnOutsideClick(() => setOpen(false));

  return (
    <div>
      <button onClick={() => setOpen(true)}>open</button>
      {open && (
        <div ref={innerBorderRef}>
           <SomeChild/>
        </div>
      )}
    </div>
  );

};

链接到演示

部分灵感来自于@pau1itzgerald的回答。

import { RefObject, useEffect } from 'react';

const useClickOutside = <T extends HTMLElement>(ref: RefObject<T>, fn: () => void) => {
    useEffect(() => {
        const element = ref?.current;
        function handleClickOutside(event: Event) {
            if (element && !element.contains(event.target as Node | null)) {
                fn();
            }
        }
        document.addEventListener('mousedown', handleClickOutside);
        return () => {
            document.removeEventListener('mousedown', handleClickOutside);
        };
    }, [ref]);
};

export default useClickOutside;

我对所有其他答案最担心的是必须从根/父级向下过滤单击事件。我发现最简单的方法是简单地设置一个具有位置的同级元素:fixed,下拉列表后面的z索引1,并处理同一组件内固定元素上的单击事件。将所有内容集中到给定组件。

示例代码

#HTML
<div className="parent">
  <div className={`dropdown ${this.state.open ? open : ''}`}>
    ...content
  </div>
  <div className="outer-handler" onClick={() => this.setState({open: false})}>
  </div>
</div>

#SASS
.dropdown {
  display: none;
  position: absolute;
  top: 0px;
  left: 0px;
  z-index: 100;
  &.open {
    display: block;
  }
}
.outer-handler {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    opacity: 0;
    z-index: 99;
    display: none;
    &.open {
      display: block;
    }
}
import React, { useState, useEffect, useRef } from "react";

const YourComponent: React.FC<ComponentProps> = (props) => {
  const ref = useRef<HTMLDivElement | null>(null);
  const [myState, setMyState] = useState(false);
  useEffect(() => {
    const listener = (event: MouseEvent) => {
      // we have to add some logic to decide whether or not a click event is inside of this editor
      // if user clicks on inside the div we dont want to setState
      // we add ref to div to figure out whether or not a user is clicking inside this div to determine whether or not event.target is inside the div
      if (
        ref.current &&
        event.target &&
        // contains is expect other: Node | null
        ref.current.contains(event.target as Node)
      ) {
        return;
      }
      // if we are outside
      setMyState(false);
    };
    // anytime user clics anywhere on the dom, that click event will bubble up into our body element
    // without { capture: true } it might not work
    document.addEventListener("click", listener, { capture: true });
    return () => {
      document.removeEventListener("click", listener, { capture: true });
    };
  }, []);

  return (
    <div  ref={ref}>
      ....
    </div>
  );
};

https://stackoverflow.com/a/42234988/9536897解决方案在手机上不起作用。

您可以尝试:

  // returns true if the element or one of its parents has the class classname
  hasSomeParentTheClass(element, classname) {
    if(element.target)
    element=element.target;
    
    if (element.className&& element.className.split(" ").indexOf(classname) >= 0) return true;
    return (
      element.parentNode &&
      this.hasSomeParentTheClass(element.parentNode, classname)
    );
  }
  componentDidMount() {
    const fthis = this;

    $(window).click(function (element) {
      if (!fthis.hasSomeParentTheClass(element, "myClass"))
        fthis.setState({ pharmacyFocus: null });
    });
  }

在视图中,将className赋给特定元素。