检测React组件外部的单击

Thi*_*man 315 javascript dom reactjs

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

所以在我的组件中,我想将一个单击处理程序附加到窗口.当处理程序触发时,我需要将目标与我的组件的dom子项进行比较.

click事件包含"path"之类的属性,它似乎保存了事件所经过的dom路径.我不确定要比较什么或如何最好地遍历它,我认为有人必须已经把它放在一个聪明的实用功能中......不是吗?

Ben*_*Bud 511

以下解决方案使用ES6并遵循绑定的最佳实践以及通过方法设置ref.

要看它的实际效果:Code Sandbox demo

import React, { Component } from 'react';

/**
 * Component that alerts if you click outside of it
 */
export default class OutsideAlerter extends Component {
  constructor(props) {
    super(props);

    this.setWrapperRef = this.setWrapperRef.bind(this);
    this.handleClickOutside = this.handleClickOutside.bind(this);
  }

  componentDidMount() {
    document.addEventListener('mousedown', this.handleClickOutside);
  }

  componentWillUnmount() {
    document.removeEventListener('mousedown', this.handleClickOutside);
  }

  /**
   * Set the wrapper ref
   */
  setWrapperRef(node) {
    this.wrapperRef = node;
  }

  /**
   * Alert if clicked on outside of element
   */
  handleClickOutside(event) {
    if (this.wrapperRef && !this.wrapperRef.contains(event.target)) {
      alert('You clicked outside of me!');
    }
  }

  render() {
    return <div ref={this.setWrapperRef}>{this.props.children}</div>;
  }
}

OutsideAlerter.propTypes = {
  children: PropTypes.element.isRequired,
};
Run Code Online (Sandbox Code Playgroud)

  • @ polkovnikov.ph 1.如果使用了`context`,它只是构造函数中的参数.在这种情况下不使用它.反应团队建议在构造函数中使用`props`作为参数的原因是因为在调用`super(props)`之前使用`this.props`将是未定义的并且可能导致错误.`context`仍然可以在内部节点上使用,这就是`context`的全部目的.这样您就不必像使用props那样将组件从组件传递给组件.这只是一种风格偏好,并不保证在这种情况下的辩论. (7认同)
  • 谢谢你。它适用于我的一些情况,但不适用于“display:absolute”的弹出窗口 - 当您单击任何地方(包括内部)时,它总是会触发“handleClickOutside”。最终切换到react-outside-click-handler,这似乎以某种方式涵盖了这种情况 (5认同)
  • 你怎么能在这里使用React的合成事件而不是`document.addEventListener`? (4认同)
  • 我收到以下错误:"this.wrapperRef.contains不是函数" (4认同)
  • @Bogdan,我在使用样式组件时遇到了同样的错误.考虑在顶层使用<div> (4认同)
  • 这个答案错过了“setWrapperRef”方法。值得庆幸的是,可以在 https://codesandbox.io/s/30q3mzjv91?module=%2Fsrc%2FOutsideAlerter.js 找到它。 (3认同)
  • 你怎么测试这个?如何将有效的event.target发送到handleClickOutside函数? (2认同)
  • 在测试中,您可以渲染<div>附近的<OutsideAlerter>.模拟<div>上的单击并通过使用诸如jest mocks或sinon spies之类的内容来监听被调用的警报方法 (2认同)
  • 需要注意的是,当使用键盘聚焦(选项卡)并激活(输入)组件外部的按钮或链接时,不会触发 _mousedown_ 事件。然而,_click_ 事件还将包括键盘使用。因此,在某些情况下,将侦听器添加到 _click_ 事件可能更可取。 (2认同)
  • @BenBud 这打破了嵌套门户 - 示例 - https://codesandbox.io/s/yvlm8lor7v 解释(twitter 线程) - https://twitter.com/gdad_s_river/status/987725719215144960 我可以使用一些建议来改进此组件与门户网站配合良好。 (2认同)
  • 任何有@Bogdan 问题的人,建议的解决方案是正确的:只需添加一个容器 div 并将 ref 属性放置在那里,而不是您的实际组件。那个有效。 (2认同)
  • 对于钩子实现,为什么要在每次渲染时读取和删除侦听器,为什么不只是在第一次安装时将“[]”作为第二个参数传递给 useEffect 呢? (2认同)

Pab*_*nda 128

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

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

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

你也可以通过JavaScript来实现 element.setAttribute('tabindex',0)

这就是我用它来制作自定义的DropDown菜单.

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>
        );
    }
});
Run Code Online (Sandbox Code Playgroud)

  • 我在某种程度上"工作",这是一个很酷的小黑客.我的问题是我的下拉内容是`display:absolute`,因此下拉列表不会影响父div的大小.这意味着当我点击下拉列表中的某个项目时,onblur会触发. (13认同)
  • 这不会产生可访问性问题吗?因为你可以使一个额外的元素可以专注于检测何时进入/关闭焦点,但是交互应该在下拉值上没有? (9认同)
  • 如果下拉内容包含一些可聚焦元素(例如表单输入),则可能会遇到其他一些问题.他们会偷你的焦点,'onBlur`将在你的下拉容器中触发,`expanded`将被设置为false. (5认同)
  • 要单击目标内的元素,请参阅此答案:/sf/answers/3106518061/ (4认同)
  • 这是一个非常有趣的方法.完全适合我的情况,但我有兴趣了解这可能会如何影响可访问性. (2认同)
  • 我遇到了这种方法的问题,下拉内容中的任何元素都不会触发onClick之类的事件. (2认同)

Pau*_*ald 87

我被困在同一个问题上.我在这里参加派对有点晚了,但对我来说这是一个非常好的解决方案.希望它对别人有帮助.你需要导入findDOMNodereact-dom

import ReactDOM from 'react-dom';
// ... ?

componentDidMount() {
    document.addEventListener('click', this.handleClickOutside, true);
}

componentWillUnmount() {
    document.removeEventListener('click', this.handleClickOutside, true);
}

handleClickOutside = event => {
    const domNode = ReactDOM.findDOMNode(this);

    if (!domNode || !domNode.contains(event.target)) {
        this.setState({
            visible: false
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

React Hooks方法(16.8 +)

您可以创建一个可重用的钩子useComponentVisible.

import { useState, useEffect, useRef } from 'react';

export default function useComponentVisible(initialIsVisible) {
    const [isComponentVisible, setIsComponentVisible] = useState(initialIsVisible);
    const ref = useRef(null);

    const handleClickOutside = (event) => {
        if (ref.current && !ref.current.contains(event.target)) {
            setIsComponentVisible(false);
        }
    };

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

    return { ref, isComponentVisible, setIsComponentVisible };
}
Run Code Online (Sandbox Code Playgroud)

然后在组件中添加要执行以下操作的功能:

const DropDown = () => {
    const { ref, isComponentVisible } = useComponentVisible(true);
    return (
       <div ref={ref}>
          {isComponentVisible && (<p>Dropdown Component</p>)}
       </div>
    );

}
Run Code Online (Sandbox Code Playgroud)

在此处查找codesandbox示例.

  • 这应该是公认的答案.完美适用于具有绝对定位的下拉菜单. (3认同)
  • `ReactDOM.findDOMNode` 已弃用,应使用 `ref` 回调:https://github.com/yannickcr/eslint-plugin-react/issues/678#issue-165177220 (3认同)
  • 这很棒,因为它可以重复使用。完美的。 (3认同)
  • 伟大而干净的解决方案 (2认同)
  • 这是我使用 TS 进行的打字设置,似乎有效:`const ref = useRef&lt;HTMLDivElement&gt;(null); const handleClickOutside = (event: MouseEvent) =&gt; { const target = event.target as HTMLElement; if (ref.current &amp;&amp; !ref.current.contains(target)) { setIsComponentVisible(false); } }};` (2认同)

Bea*_*ith 84

在尝试了很多方法之后,我决定使用github.com/Pomax/react-onclickoutside,因为它有多完整.

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

import onClickOutside from 'react-onclickoutside'
Run Code Online (Sandbox Code Playgroud)

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

handleClickOutside = () => {
  console.log('onClickOutside() method called')
}
Run Code Online (Sandbox Code Playgroud)

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

export default onClickOutside(NameOfComponent)
Run Code Online (Sandbox Code Playgroud)

而已.

  • 最好是使用专用组件然后使用tabIndex hack.Upvoting它! (4认同)
  • 使用tabindex的另一个优点是,如果您专注于子元素,tabindex解决方案也会触发模糊事件 (4认同)
  • @MarkAmery - 我对`tabIndex/onBlur`方法发表评论.当下拉列表是"position:absolute"时,它不起作用,例如悬停在其他内容上的菜单. (3认同)
  • 对于Pablo提出的[`tabIndex` /`onBlur`方法](http://stackoverflow.com/a/37491578/1709587),是否有任何具体优势?实现如何工作,它的行为与`onBlur`方法有何不同? (2认同)

Thi*_*man 38

感谢Ben Alpert在discuss.reactjs.org上找到了解决方案.建议的方法将处理程序附加到文档,但结果证明是有问题的.单击树中的某个组件会导致重新渲染,删除更新时单击的元素.因为React的rerender 调用文档正文处理程序之前发生,所以该元素未被检测为树的"内部".

解决方案是在应用程序根元素上添加处理程序.

主要:

window.__myapp_container = document.getElementById('app')
React.render(<App/>, window.__myapp_container)
Run Code Online (Sandbox Code Playgroud)

零件:

import { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';

export default class ClickListener extends Component {

  static propTypes = {
    children: PropTypes.node.isRequired,
    onClickOutside: PropTypes.func.isRequired
  }

  componentDidMount () {
    window.__myapp_container.addEventListener('click', this.handleDocumentClick)
  }

  componentWillUnmount () {
    window.__myapp_container.removeEventListener('click', this.handleDocumentClick)
  }

  /* using fat arrow to bind to instance */
  handleDocumentClick = (evt) => {
    const area = ReactDOM.findDOMNode(this.refs.area);

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

  render () {
    return (
      <div ref='area'>
       {this.props.children}
      </div>
    )
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 这对我来说不再适用于React 0.14.7 - 也许React改变了一些东西,或者在将代码调整为React的所有更改时我犯了一个错误.我改为使用https://github.com/Pomax/react-onclickoutside,它就像一个魅力. (8认同)
  • 一个纯粹的用户体验点:在这里,`mousedown` 可能是比 `click` 更好的处理程序。在大多数应用程序中,通过单击外部关闭菜单的行为发生在您按下鼠标的那一刻,而不是您松开鼠标的那一刻。例如,尝试使用 Stack Overflow 的 *flag* 或 *share* 对话或浏览器顶部菜单栏中的下拉菜单之一。 (2认同)

Niy*_*yaz 28

这里没有其他答案对我有用.我试图隐藏模糊的弹出窗口,但由于内容是绝对定位的,onBlur甚至在点击内部内容时也开始了.

这是一种对我有用的方法:

// Inside the component:
onBlur(event) {
    // currentTarget refers to this component.
    // relatedTarget refers to the element where the user clicked (or focused) which
    // triggered this event.
    // So in effect, this condition checks if the user clicked outside the component.
    if (!event.currentTarget.contains(event.relatedTarget)) {
        // do your thing.
    }
},
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助.

  • 诚实地应该是公认的答案,最干净的解决方案 ITT (3认同)
  • 谢谢!帮我很多。 (2认同)
  • 我比那些附加到`document`的方法更喜欢这个。谢谢。 (2认同)
  • 为此,您需要确保单击的元素(相关目标)是可聚焦的。请参阅/sf/ask/2993514611/ (2认同)

for*_*d04 28

使用React Hooks(16.8 +)的可重用解决方案

密码箱

创建外部单击通知挂钩:

function useOuterClickNotifier(onOuterClick, innerRef) {
  useEffect(
    () => {
      // only add listener, if the element exists
      if (innerRef.current) {
        document.addEventListener("click", handleClick);
      }

      // unmount previous first in case inputs have changed
      return () => document.removeEventListener("click", handleClick);

      function handleClick(e) {
        innerRef.current && !innerRef.current.contains(e.target) && onOuterClick(e);
      }
    },
    [onOuterClick, innerRef] // invoke again, if inputs have changed
  );
}
Run Code Online (Sandbox Code Playgroud)

在以下任何组件中使用挂钩:

const InnerComp = () => {
  const innerRef = useRef(null);
  useOuterClickNotifier(
    // if you want to optimize performance a bit,
    // don't provide an anonymous function here
    // See link down under (*1)
    e => alert("clicked outside of this component!"),
    innerRef
  );
  return (
    <div ref={innerRef}>
      inside component
    </div>
  );
}

Run Code Online (Sandbox Code Playgroud)

* 1 通过跳过效果来优化性能

然后,根据您的用例,您可以在外部click回调中执行某些操作,为简化起见,此处将其存入存根alert("clicked outside of this component!")。例如,使用useStateHook 设置某些状态或调用给定的回调。


优于课堂解决方案的优势:

  • 外部点击副作用逻辑可以被封装(关注点分离)并简化消耗组件。
  • useOuterClickNotifier 钩子可以在所有组件中重复使用,而无需像在类解决方案中那样需要包装器组件/渲染道具( Link)中。
  • 从长远来看,React团队希望Hooks成为人们编写React组件(Link)的主要方式。

缺点:

  • 您不得useOuterClickNotifier在其他类组件(链接)中使用,另请参见此处

附加信息:带有可点击元素的IOS怪癖(影响mousedownclick

IOS仅将某些元素视为可点击的-有关quirksmodeSO答案以及此处的更多信息。为避免这种情况,请选择其他外部点击侦听器元素(不包括在内body)。例如,您可以<div id="root"></div>改为注册反应根的点击,并将其高度扩展到整个视口(查看IOS Codesandbox)。或什至更好的做法是:完全避免使用全局变量,并将一个显式元素传递给useOuterClickNotifierHook,该元素可用于注册外部点击。

感谢@Omar的提示,感谢@Kostas Sarantopoulos提供CSS媒体查询的其他替代方法(请参阅评论)。

希望有帮助。

  • @ford04感谢您的挖掘,我偶然发现了另一个[线程](/sf/ask/740195991/),这表明下面的技巧。`@media (hover: none) and (pointer: rough) { body {cursor:pointer } }` 在我的全局样式中添加此内容,似乎解决了问题。 (2认同)

ono*_*oya 18

[更新]使用Hooks使用React ^ 16.8的解决方案

CodeSandbox

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

const SampleComponent = () => {
    const [clickedOutside, setClickedOutside] = useState(false);
    const myRef = useRef();

    const handleClickOutside = e => {
        if (!myRef.current.contains(e.target)) {
            setClickedOutside(true);
        }
    };

    const handleClickInside = () => setClickedOutside(false);

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

    return (
        <button ref={myRef} onClick={handleClickInside}>
            {clickedOutside ? 'Bye!' : 'Hello!'}
        </button>
    );
};

export default SampleComponent;
Run Code Online (Sandbox Code Playgroud)

使用React解决方案^ 16.3:

CodeSandbox

import React, { Component } from "react";

class SampleComponent extends Component {
  state = {
    clickedOutside: false
  };

  componentDidMount() {
    document.addEventListener("mousedown", this.handleClickOutside);
  }

  componentWillUnmount() {
    document.removeEventListener("mousedown", this.handleClickOutside);
  }

  myRef = React.createRef();

  handleClickOutside = e => {
    if (!this.myRef.current.contains(e.target)) {
      this.setState({ clickedOutside: true });
    }
  };

  handleClickInside = () => this.setState({ clickedOutside: false });

  render() {
    return (
      <button ref={this.myRef} onClick={this.handleClickInside}>
        {this.state.clickedOutside ? "Bye!" : "Hello!"}
      </button>
    );
  }
}

export default SampleComponent;
Run Code Online (Sandbox Code Playgroud)

  • 这是现在的首选解决方案.如果您收到错误`.contains不是函数`,可能是因为您将ref prop传递给自定义组件而不是真正的DOM元素,如`<div>` (3认同)

小智 15

带打字稿

function Tooltip(): ReactElement {
  const [show, setShow] = useState(false);
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    function handleClickOutside(event: MouseEvent): void {
      if (ref.current && !ref.current.contains(event.target as Node)) {
        setShow(false);
      }
    }
    // 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>
  ) 
 }
Run Code Online (Sandbox Code Playgroud)


小智 13

或者:

const onClickOutsideListener = () => {
    alert("click outside")
    document.removeEventListener("click", onClickOutsideListener)
  }

...

return (
  <div
    onMouseLeave={() => {
          document.addEventListener("click", onClickOutsideListener)
        }}
  >
   ...
  </div>
Run Code Online (Sandbox Code Playgroud)


Oli*_*ari 11

Material-UI 有一个小组件来解决这个问题:https : //material-ui.com/components/click-away-listener/你可以随意挑选它。压缩后重量为 1.5 kB,支持移动、IE 11 和门户。


Kuz*_*ave 11

Ez方式...

const componentRef = useRef();

useEffect(() => {
    document.addEventListener("click", handleClick);
    return () => document.removeEventListener("click", handleClick);
    function handleClick(e: any) {
        if(componentRef && componentRef.current){
            const ref: any = componentRef.current
            if(!ref.contains(e.target)){
                // put your action here
            }
        }
    }
}, []);
Run Code Online (Sandbox Code Playgroud)

然后将 ref 放在您的组件上

<div ref={componentRef as any}> My Component </div>
Run Code Online (Sandbox Code Playgroud)

  • 有了这个,我不需要任何反应包。多谢兄弟。 (3认同)

小智 7

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

<ClickAwayListener onClickAway={handleClickAway}>
    {children}
<ClickAwayListener >
Run Code Online (Sandbox Code Playgroud)

有关更多信息,您可以检查:https ://mui.com/base/react-click-away-listener/


Seb*_*sen 6

Typescript + @ford04 提案的简化版本:

useOuterClick应用程序编程接口

const Client = () => {
  const ref = useOuterClick<HTMLDivElement>(e => { /* Custom-event-handler */ });
  return <div ref={ref}> Inside </div> 
};
Run Code Online (Sandbox Code Playgroud)

执行

export default function useOuterClick<T extends HTMLElement>(callback: Function) {
  const callbackRef = useRef<Function>(); // initialize mutable ref, which stores callback
  const innerRef = useRef<T>(null); // returned to client, who marks "border" element

  // update cb on each render, so second useEffect has access to current value
  useEffect(() => { callbackRef.current = callback; });

  useEffect(() => {
    document.addEventListener("click", _onClick);
    return () => document.removeEventListener("click", _onClick);
    function _onClick(e: any): void {
      const clickedOutside = !(innerRef.current?.contains(e.target));
      if (clickedOutside)
        callbackRef.current?.(e);
    }
  }, []); // no dependencies -> stable click listener

  return innerRef; // convenience for client (doesn't need to init ref himself)
}
Run Code Online (Sandbox Code Playgroud)


pie*_*e6k 5

这是我的方法(演示-https: //jsfiddle.net/agymay93/4/):

我创建了一个称为的特殊组件WatchClickOutside,它可以像(我假设JSX语法)那样使用:

<WatchClickOutside onClickOutside={this.handleClose}>
  <SomeDropdownEtc>
</WatchClickOutside>
Run Code Online (Sandbox Code Playgroud)

这是WatchClickOutside组件的代码:

import React, { Component } from 'react';

export default class WatchClickOutside extends Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

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

  componentWillUnmount() {
    // remember to remove all events to avoid memory leaks
    document.body.removeEventListener('click', this.handleClick);
  }

  handleClick(event) {
    const {container} = this.refs; // get container that we'll wait to be clicked outside
    const {onClickOutside} = this.props; // get click outside callback
    const {target} = event; // get direct click event target

    // if there is no proper callback - no point of checking
    if (typeof onClickOutside !== 'function') {
      return;
    }

    // if target is container - container was not clicked outside
    // if container contains clicked target - click was not outside of it
    if (target !== container && !container.contains(target)) {
      onClickOutside(event); // clicked outside - fire callback
    }
  }

  render() {
    return (
      <div ref="container">
        {this.props.children}
      </div>
    );
  }
}
Run Code Online (Sandbox Code Playgroud)


Ant*_*ant 5

对于那些需要绝对定位的人,我选择的一个简单选项是添加一个包装组件,该组件的样式设置为以透明背景覆盖整个页面。然后你可以在这个元素上添加一个 onClick 来关闭你的内部组件。

<div style={{
        position: 'fixed',
        top: '0', right: '0', bottom: '0', left: '0',
        zIndex: '1000',
      }} onClick={() => handleOutsideClick()} >
    <Content style={{position: 'absolute'}}/>
</div>
Run Code Online (Sandbox Code Playgroud)

就像现在,如果您在内容上添加点击处理程序,该事件也将传播到上方的 div 并因此触发 handlerOutsideClick。如果这不是您想要的行为,只需停止处理程序上的事件传播即可。

<Content style={{position: 'absolute'}} onClick={e => {
                                          e.stopPropagation();
                                          desiredFunctionCall();
                                        }}/>
Run Code Online (Sandbox Code Playgroud)

`

  • 这个解决方案应该得到更多支持。除了易于实现之外,它的主要优点之一是它禁用与其他元素的交互。这是本机警报和下拉列表的默认行为,因此应该用于模态和下拉列表的所有自定义实现。 (2认同)

Raw*_*ode 5

我部分是通过遵循this并遵循React官方文档处理需要react ^ 16.3的refs来做到这一点的。在尝试了这里的其他一些建议之后,这是唯一对我有用的东西......

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>
    )
  }
}
Run Code Online (Sandbox Code Playgroud)


Ric*_*ies 5

这已经有很多答案,但是它们没有解决,e.stopPropagation()并且无法单击您要关闭的元素之外的react链接。

由于React具有自己的人工事件处理程序,因此您无法将文档用作事件侦听器的基础。e.stopPropagation()在此之前,您需要做为React使用文档本身。如果使用示例document.querySelector('body')代替。您可以阻止来自React链接的点击。以下是我如何实现外部点击和关闭的示例。
这使用了ES6React 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;
Run Code Online (Sandbox Code Playgroud)


Mel*_*nek 5

import { useClickAway } from "react-use";

useClickAway(ref, () => console.log('OUTSIDE CLICKED'));
Run Code Online (Sandbox Code Playgroud)


小智 5

带 Hook 的打字稿

注意:我使用的是 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>
  );
}

Run Code Online (Sandbox Code Playgroud)