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

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

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


当前回答

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

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>
    )
}

其他回答

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

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

import onClickOutside from 'react-onclickoutside'

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

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

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

export default onClickOutside(NameOfComponent)

就是这样。

componentWillMount(){

  document.addEventListener('mousedown', this.handleClickOutside)
}

handleClickOutside(event) {

  if(event.path[0].id !== 'your-button'){
     this.setState({showWhatever: false})
  }
}

事件路径[0]是最后单击的项目

Ez的方式。。。(2022年更新)

创建挂钩:useOutsideClick.ts

export function useOutsideClick(ref: any, onClickOut: () => void){
    useEffect(() => {
        const onClick = ({target}: any) => !ref.contains(target) && onClickOut?.()
        document.addEventListener("click", onClick);
        return () => document.removeEventListener("click", onClick);
    }, []);
}

将componentRef添加到组件并调用useOutsideClick

export function Example(){

  const componentRef = useRef();

  useOutsideClick(componentRef.current!, () => {
   // do something here
  });

  return ( 
    <div ref={componentRef as any}> My Component </div>
  )
}

2021更新:

自从我添加这个响应以来,已经有一段时间了,而且由于它似乎仍然引起了一些兴趣,我想我会将它更新到更新的React版本。2021,我会这样写这个组件:

import React, { useState } from "react";
import "./DropDown.css";

export function DropDown({ options, callback }) {
    const [selected, setSelected] = useState("");
    const [expanded, setExpanded] = useState(false);

    function expand() {
        setExpanded(true);
    }

    function close() {
        setExpanded(false);
    }

    function select(event) {
        const value = event.target.textContent;
        callback(value);
        close();
        setSelected(value);
    }

    return (
        <div className="dropdown" tabIndex={0} onFocus={expand} onBlur={close} >
            <div>{selected}</div>
            {expanded ? (
                <div className={"dropdown-options-list"}>
                    {options.map((O) => (
                        <div className={"dropdown-option"} onClick={select}>
                            {O}
                        </div>
                    ))}
                </div>
            ) : null}
        </div>
    );
}

原始答案(2016):

以下是最适合我的解决方案,无需将事件附加到容器:

某些HTML元素可以具有所谓的“焦点”,例如输入元素。当这些元素失去焦点时,它们也会对模糊事件做出响应。

要使任何元素具有焦点的能力,只需确保其tabindex属性设置为-1以外的任何值。在常规HTML中,这是通过设置tabindex属性实现的,但在React中,必须使用tabindex(注意大写I)。

您也可以通过JavaScript使用element.setAttribute('tabindex',0)执行此操作

这就是我用来制作自定义下拉菜单的原因。

var DropDownMenu = React.createClass({
    getInitialState: function(){
        return {
            expanded: false
        }
    },
    expand: function(){
        this.setState({expanded: true});
    },
    collapse: function(){
        this.setState({expanded: false});
    },
    render: function(){
        if(this.state.expanded){
            var dropdown = ...; //the dropdown content
        } else {
            var dropdown = undefined;
        }
        
        return (
            <div className="dropDownMenu" tabIndex="0" onBlur={ this.collapse } >
                <div className="currentValue" onClick={this.expand}>
                    {this.props.displayValue}
                </div>
                {dropdown}
            </div>
        );
    }
});

要使“焦点”解决方案适用于带有事件侦听器的下拉列表,您可以使用onMouseDown事件而不是onClick来添加它们。这样,事件将启动,之后弹出窗口将关闭,如下所示:

<TogglePopupButton
                    onClick = { this.toggleDropup }
                    tabIndex = '0'
                    onBlur = { this.closeDropup }
                />
                { this.state.isOpenedDropup &&
                <ul className = { dropupList }>
                    { this.props.listItems.map((item, i) => (
                        <li
                            key = { i }
                            onMouseDown = { item.eventHandler }
                        >
                            { item.itemName}
                        </li>
                    ))}
                </ul>
                }