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

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

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


当前回答

componentWillMount(){

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

handleClickOutside(event) {

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

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

其他回答

带钩的字体

注意:我使用的是React 16.3版,带有React.createRef。对于其他版本,使用ref回调。

下拉组件:

interface DropdownProps {
 ...
};

export const Dropdown: React.FC<DropdownProps> () {
  const ref: React.RefObject<HTMLDivElement> = React.createRef();
  
  const handleClickOutside = (event: MouseEvent) => {
    if (ref && ref !== null) {
      const cur = ref.current;
      if (cur && !cur.contains(event.target as Node)) {
        // close all dropdowns
      }
    }
  }

  useEffect(() => {
    // Bind the event listener
    document.addEventListener("mousedown", handleClickOutside);
    return () => {
      // Unbind the event listener on clean up
      document.removeEventListener("mousedown", handleClickOutside);
    };
  });

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

将onClick处理程序添加到顶级容器中,并在用户单击时增加状态值。将该值传递给相关组件,每当该值发生变化时,您就可以执行操作。

在本例中,每当clickCount值更改时,我们都会调用this.closeDropdown()。

incrementClickCount方法在.app容器中激发,而不是在下拉列表中激发,因为我们使用event.stopPropagation()来防止事件冒泡。

您的代码可能最终看起来像这样:

class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            clickCount: 0
        };
    }
    incrementClickCount = () => {
        this.setState({
            clickCount: this.state.clickCount + 1
        });
    }
    render() {
        return (
            <div className="app" onClick={this.incrementClickCount}>
                <Dropdown clickCount={this.state.clickCount}/>
            </div>
        );
    }
}
class Dropdown extends Component {
    constructor(props) {
        super(props);
        this.state = {
            open: false
        };
    }
    componentDidUpdate(prevProps) {
        if (this.props.clickCount !== prevProps.clickCount) {
            this.closeDropdown();
        }
    }
    toggleDropdown = event => {
        event.stopPropagation();
        return (this.state.open) ? this.closeDropdown() : this.openDropdown();
    }
    render() {
        return (
            <div className="dropdown" onClick={this.toggleDropdown}>
                ...
            </div>
        );
    }
}

我之所以这样做,部分原因是遵循了这一点,并遵循了React官方文件关于处理需要React ^16.3的参考文献。这是我在尝试了其他一些建议之后唯一有效的方法。。。

class App extends Component {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
  }
  componentWillMount() {
    document.addEventListener("mousedown", this.handleClick, false);
  }
  componentWillUnmount() {
    document.removeEventListener("mousedown", this.handleClick, false);
  }
  handleClick = e => {
    /*Validating click is made inside a component*/
    if ( this.inputRef.current === e.target ) {
      return;
    }
    this.handleclickOutside();
  };
  handleClickOutside(){
    /*code to handle what to do when clicked outside*/
  }
  render(){
    return(
      <div>
        <span ref={this.inputRef} />
      </div>
    )
  }
}

这已经有很多答案了,但它们没有解决e.stopPropagation()和阻止单击要关闭的元素之外的react链接的问题。

由于React有自己的人工事件处理程序,您无法将文档用作事件侦听器的基础。在这之前,您需要e.stopPropagation(),因为React使用文档本身。如果改用document.querySelector('body')。您可以防止点击React链接。下面是我如何实现单击外部并关闭的示例。这使用ES6和React 16.3。

import React, { Component } from 'react';

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      isOpen: false,
    };

    this.insideContainer = React.createRef();
  }

  componentWillMount() {
    document.querySelector('body').addEventListener("click", this.handleClick, false);
  }

  componentWillUnmount() {
    document.querySelector('body').removeEventListener("click", this.handleClick, false);
  }

  handleClick(e) {
    /* Check that we've clicked outside of the container and that it is open */
    if (!this.insideContainer.current.contains(e.target) && this.state.isOpen === true) {
      e.preventDefault();
      e.stopPropagation();
      this.setState({
        isOpen: false,
      })
    }
  };

  togggleOpenHandler(e) {
    e.preventDefault();

    this.setState({
      isOpen: !this.state.isOpen,
    })
  }

  render(){
    return(
      <div>
        <span ref={this.insideContainer}>
          <a href="#open-container" onClick={(e) => this.togggleOpenHandler(e)}>Open me</a>
        </span>
        <a href="/" onClick({/* clickHandler */})>
          Will not trigger a click when inside is open.
        </a>
      </div>
    );
  }
}

export default App;

所以我遇到了一个类似的问题,但在我的案例中,这里选择的答案不起作用,因为我有一个下拉菜单按钮,这是文档的一部分。因此,单击该按钮也会触发handleClickOutside函数。为了防止触发,我必须向按钮和这个添加一个新的引用!menuBtnRef.current.contents(e.target)设置为条件。如果有人像我一样面临同样的问题,我就把它留在这里。

下面是组件现在的样子:


const Component = () => {

    const [isDropdownOpen, setIsDropdownOpen] = useState(false);
    const menuRef     = useRef(null);
    const menuBtnRef  = useRef(null);

    const handleDropdown = (e) => {
        setIsDropdownOpen(!isDropdownOpen);
    }

    const handleClickOutside = (e) => {
        if (menuRef.current && !menuRef.current.contains(e.target) && !menuBtnRef.current.contains(e.target)) {
            setIsDropdownOpen(false);
        }
    }

    useEffect(() => {
        document.addEventListener('mousedown', handleClickOutside, true);
        return () => {
            document.removeEventListener('mousedown', handleClickOutside, true);
        };
    }, []);

    return (

           <button ref={menuBtnRef} onClick={handleDropdown}></button>

           <div ref={menuRef} className={`${isDropdownOpen ? styles.dropdownMenuOpen : ''}`}>
                // ...dropdown items
           </div>
    )
}