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

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

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


当前回答

战略示例

我喜欢所提供的解决方案,这些解决方案通过围绕组件创建包装器来完成相同的任务。

由于这更多是一种行为,我想到了战略,并提出了以下建议。

我是React的新手,我需要一些帮助来保存用例中的样板

请回顾并告诉我你的想法。

ClickOutside行为

import ReactDOM from 'react-dom';

export default class ClickOutsideBehavior {

  constructor({component, appContainer, onClickOutside}) {

    // Can I extend the passed component's lifecycle events from here?
    this.component = component;
    this.appContainer = appContainer;
    this.onClickOutside = onClickOutside;
  }

  enable() {

    this.appContainer.addEventListener('click', this.handleDocumentClick);
  }

  disable() {

    this.appContainer.removeEventListener('click', this.handleDocumentClick);
  }

  handleDocumentClick = (event) => {

    const area = ReactDOM.findDOMNode(this.component);

    if (!area.contains(event.target)) {
        this.onClickOutside(event)
    }
  }
}

示例用法

import React, {Component} from 'react';
import {APP_CONTAINER} from '../const';
import ClickOutsideBehavior from '../ClickOutsideBehavior';

export default class AddCardControl extends Component {

  constructor() {
    super();

    this.state = {
      toggledOn: false,
      text: ''
    };

    this.clickOutsideStrategy = new ClickOutsideBehavior({
      component: this,
      appContainer: APP_CONTAINER,
      onClickOutside: () => this.toggleState(false)
    });
  }

  componentDidMount () {

    this.setState({toggledOn: !!this.props.toggledOn});
    this.clickOutsideStrategy.enable();
  }

  componentWillUnmount () {
    this.clickOutsideStrategy.disable();
  }

  toggleState(isOn) {

    this.setState({toggledOn: isOn});
  }

  render() {...}
}

笔记

我想到了存储传递的组件生命周期挂钩,并用类似的方法覆盖它们:

const baseDidMount = component.componentDidMount;

component.componentDidMount = () => {
  this.enable();
  baseDidMount.call(component)
}

component是传递给ClickOutsideBehavior构造函数的组件。这将从该行为的用户中删除启用/禁用样板,但看起来不太好

其他回答

我使用了这个模块(我与作者没有关联)

npm install react-onclickout --save

const ClickOutHandler=require('act-on-clickout');ExampleComponent类扩展React.Component{单击退出(e){if(hasClass(e.target,'忽略我'))返回;alert('用户在组件外部单击!');}render(){返回(<ClickOutHandler onClickOut={this.onClickOut}><div>点击我的外部</分区></ClickOutHandler>);}}

它做得很好。

战略示例

我喜欢所提供的解决方案,这些解决方案通过围绕组件创建包装器来完成相同的任务。

由于这更多是一种行为,我想到了战略,并提出了以下建议。

我是React的新手,我需要一些帮助来保存用例中的样板

请回顾并告诉我你的想法。

ClickOutside行为

import ReactDOM from 'react-dom';

export default class ClickOutsideBehavior {

  constructor({component, appContainer, onClickOutside}) {

    // Can I extend the passed component's lifecycle events from here?
    this.component = component;
    this.appContainer = appContainer;
    this.onClickOutside = onClickOutside;
  }

  enable() {

    this.appContainer.addEventListener('click', this.handleDocumentClick);
  }

  disable() {

    this.appContainer.removeEventListener('click', this.handleDocumentClick);
  }

  handleDocumentClick = (event) => {

    const area = ReactDOM.findDOMNode(this.component);

    if (!area.contains(event.target)) {
        this.onClickOutside(event)
    }
  }
}

示例用法

import React, {Component} from 'react';
import {APP_CONTAINER} from '../const';
import ClickOutsideBehavior from '../ClickOutsideBehavior';

export default class AddCardControl extends Component {

  constructor() {
    super();

    this.state = {
      toggledOn: false,
      text: ''
    };

    this.clickOutsideStrategy = new ClickOutsideBehavior({
      component: this,
      appContainer: APP_CONTAINER,
      onClickOutside: () => this.toggleState(false)
    });
  }

  componentDidMount () {

    this.setState({toggledOn: !!this.props.toggledOn});
    this.clickOutsideStrategy.enable();
  }

  componentWillUnmount () {
    this.clickOutsideStrategy.disable();
  }

  toggleState(isOn) {

    this.setState({toggledOn: isOn});
  }

  render() {...}
}

笔记

我想到了存储传递的组件生命周期挂钩,并用类似的方法覆盖它们:

const baseDidMount = component.componentDidMount;

component.componentDidMount = () => {
  this.enable();
  baseDidMount.call(component)
}

component是传递给ClickOutsideBehavior构造函数的组件。这将从该行为的用户中删除启用/禁用样板,但看起来不太好

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

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

import onClickOutside from 'react-onclickoutside'

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

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

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

export default onClickOutside(NameOfComponent)

就是这样。

在我的DROPDOWN案例中,Ben Bud的解决方案工作得很好,但我有一个单独的切换按钮和一个onClick处理程序。因此,外部单击逻辑与单击切换按钮冲突。下面是我如何通过传递按钮的ref来解决这个问题:

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

/**
 * Hook that triggers onClose when clicked outside of ref and buttonRef elements
 */
function useOutsideClicker(ref, buttonRef, onOutsideClick) {
  useEffect(() => {

    function handleClickOutside(event) {
      /* clicked on the element itself */
      if (ref.current && !ref.current.contains(event.target)) {
        return;
      }

      /* clicked on the toggle button */
      if (buttonRef.current && !buttonRef.current.contains(event.target)) {
        return;
      }

      /* If it's something else, trigger onClose */
      onOutsideClick();
    }

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

/**
 * Component that alerts if you click outside of it
 */
export default function DropdownMenu(props) {
  const wrapperRef = useRef(null);
  const buttonRef = useRef(null);
  const [dropdownVisible, setDropdownVisible] = useState(false);

  useOutsideClicker(wrapperRef, buttonRef, closeDropdown);

  const toggleDropdown = () => setDropdownVisible(visible => !visible);

  const closeDropdown = () => setDropdownVisible(false);

  return (
    <div>
      <button onClick={toggleDropdown} ref={buttonRef}>Dropdown Toggler</button>
      {dropdownVisible && <div ref={wrapperRef}>{props.children}</div>}
    </div>
  );
}

我知道这是一个老问题,但我不断遇到这个问题,我很难用简单的格式来解决这个问题。所以,如果这能让任何人的生活变得轻松一点,请使用airbnb的OutsideClickHandler。这是一个最简单的插件,无需编写自己的代码即可完成此任务。

例子:

hideresults(){
   this.setState({show:false})
}
render(){
 return(
 <div><div onClick={() => this.setState({show:true})}>SHOW</div> {(this.state.show)? <OutsideClickHandler onOutsideClick={() => 
  {this.hideresults()}} > <div className="insideclick"></div> </OutsideClickHandler> :null}</div>
 )
}