如何在React嵌套组件中防止事件冒泡?

225

以下是基本组件。<ul>和<li>都有onClick函数。我只想让<li>上的onClick执行,而不是<ul>。我该怎么做?

我已经尝试了e.preventDefault()、e.stopPropagation(),但都没有用。

class List extends React.Component {
  constructor(props) {
    super(props);
  }

  handleClick() {
    // do something
  }

  render() {

    return (
      <ul 
        onClick={(e) => {
          console.log('parent');
          this.handleClick();
        }}
      >
        <li 
          onClick={(e) => {
            console.log('child');
            // prevent default? prevent propagation?
            this.handleClick();
          }}
        >
        </li>       
      </ul>
    )
  }
}

// => parent
// => child

我有同样的问题,https://stackoverflow.com/questions/44711549/how-to-prevent-react-component-click-event-bubble-to-document - user8202629
stopPropagation()。我认为在这里理解事件冒泡是有意义的:https://www.robinwieruch.de/react-event-bubbling-capturing/ - Robin Wieruch
10个回答

296

我遇到了相同的问题。我发现stopPropagation确实起作用了。我会将列表项拆分为单独的组件,如下所示:

class List extends React.Component {
  handleClick = e => {
    // do something
  }

  render() {
    return (
      <ul onClick={this.handleClick}>
        <ListItem onClick={this.handleClick}>Item</ListItem> 
      </ul>
    )
  }
}

class ListItem extends React.Component {
  handleClick = e => {
    e.stopPropagation();  //  <------ Here is the magic
    this.props.onClick();
  }

  render() {
    return (
      <li onClick={this.handleClick}>
        {this.props.children}
      </li>       
    )
  }
}

ListItem组件中,this.props.handleClick()不应该改为this.props.click吗? - blankface
7
除了使用stopPropagation,还有其他的方法吗? - Ali Sajid
2
那么像<Link>这样的本地组件怎么样? - samayo
如果您需要将参数传递给handleClick函数,该怎么办? - Manticore
3
这个解决方案是否回答了错误的问题?原帖要求ListItem处理事件,但是这个解决方案让List来处理事件。(除非我理解错了什么。) - Thomas Jay Rush

103

React使用事件委托,在document上使用单个事件侦听器处理冒泡事件,比如在此示例中的'click'事件,这意味着无法停止传播; 在您与React交互时,真实事件已经传播了。在React的合成事件上调用stopPropagation是可行的,因为React在内部处理合成事件的传播。

stopPropagation: function(e){
    e.stopPropagation();
    e.nativeEvent.stopImmediatePropagation();
}

非常有用。stopPropagation()单独使用时无法正常工作。 - pravin
1
这对我有效是因为我结合了document.addEventListener('click')和react的onClick - OtotheA
1
@rob2d,那么在使用Material-UI时如何处理它? - Italik
@Italik,上面有一个示例函数;但是你需要立即在回调函数中作为第一件事调用它,以便它不会被虚拟化/吞噬。至少据我所知是这样的。幸运的是,最近我没有再与这个问题作斗争。 - rob2d
1
这对我非常有效,谢谢。实际上,出于某种原因,我只需要使用e.stopPropagation()。关于MUI的评论,我正在使用MUI...在您的按钮的onClick(即Button或IconButton的onClick参数)中,首先让函数使用e.stopPropagation,然后执行实际应该执行的onClick操作。(例如:<Button onClick={(e) => {e.stopPropagation; setOpen(true)}}>ButtonLabel</Button>) - amota

73

DOM事件的顺序:捕获与冒泡

事件传播有两个阶段。这被称为"捕获""冒泡"

               | |                                   / \
---------------| |-----------------   ---------------| |-----------------
| element1     | |                |   | element1     | |                |
|   -----------| |-----------     |   |   -----------| |-----------     |
|   |element2  \ /          |     |   |   |element2  | |          |     |
|   -------------------------     |   |   -------------------------     |
|        Event CAPTURING          |   |        Event BUBBLING           |
-----------------------------------   -----------------------------------

捕获阶段首先发生,然后是冒泡阶段。当你使用常规的 DOM API 注册事件时,默认情况下事件将成为冒泡阶段的一部分,但可以在事件创建时指定。

// CAPTURING event
button.addEventListener('click', handleClick, true)

// BUBBLING events
button.addEventListener('click', handleClick, false)
button.addEventListener('click', handleClick)
在React中,默认情况下使用冒泡事件。
// handleClick is a BUBBLING (synthetic) event
<button onClick={handleClick}></button>

// handleClick is a CAPTURING (synthetic) event
<button onClickCapture={handleClick}></button>

让我们来看一下handleClick回调函数的内容(React):

function handleClick(e) {
  // This will prevent any synthetic events from firing after this one
  e.stopPropagation()
}
function handleClick(e) {
  // This will set e.defaultPrevented to true
  // (for all synthetic events firing after this one)
  e.preventDefault()  
}

这里没提到的替代方案

如果你在所有的事件中调用e.preventDefault(),你可以检查事件是否已经被处理过,并阻止其再次被处理:

handleEvent(e) {
  if (e.defaultPrevented) return  // Exits here if event has been handled
  e.preventDefault()

  // Perform whatever you need to here.
}

关于合成事件和原生事件的区别,请参阅 React 文档:https://reactjs.org/docs/events.html


3
我只想说除了使用 onClickCapture,其他什么都没用。然后使用 event.stopProgatation()event.stopImmediatePropagation() 最终起作用了。未来开发者注意 - 如果有一个第三方包含有你需要避免的点击处理程序,请按照上述方法操作,它将防止其触发。否则,如果你使用 onClick,它不会给你实际需要的结果。 - Mr_Antivius
4
这应该是被接受的答案。你真的不应该使用 stopPropagation。我强烈建议阅读这篇文章: https://css-tricks.com/dangers-stopping-event-propagation/ - sasi
2
event.preventDefault()event.defaultPrevented 的建议对我有用。 - Mike Willis

17

这是一种简单的方法,可以防止点击事件向下一个组件传递,然后调用您的函数。

<Button onClick={(e)=> {e.stopPropagation(); yourFunction(someParam)}}>Delete</Button>

非常好。在停止嵌套子元素的ContextMenu事件传播时有效。 - Tihomir Mitkov
太好了!我在表单头部使用了“AND onSubmit”来执行两个不同的函数,当按钮被点击时。希望听到对这种做法的评论。 - StephonA

6

这并不是100%理想的解决办法,但如果在子组件中传递props过于繁琐或者创建一个Context.Provider/Context.Consumer只用于此目的实在是太麻烦的话,并且你正在处理另一个带有自己处理程序的库,那么也可以尝试以下方法:

   function myHandler(e) { 
        e.persist(); 
        e.nativeEvent.stopImmediatePropagation();
        e.stopPropagation(); 
   }

据我所知,event.persist方法可以防止一个对象立即被扔回React的SyntheticEvent池中。因此,到达时你要获取的event 实际上已经不存在了!在React内部处理事情的方式是从父元素向下查找SyntheticEvent处理程序(特别是如果父元素有回调函数),因此这种问题会在孙子节点中出现。
只要不在诸如onMouseMove这样会不断创建事件并占用大量内存的地方调用persist(且您不是在创建类似于Grandma's Cookies之类的游戏),就应该完全没问题!
还要注意:根据在GitHub上的阅读,我们应该密切关注未来版本的React,因为它们可能最终会解决一些痛点,因为它们似乎正在朝着将折叠React代码的编译器/转换器的方向发展。

5

如果你希望嵌套元素中的动作生效,而非在其父元素上生效,则可以从父元素的动作处理器中检查目标的类型,然后根据此执行操作,即,如果目标是我们的嵌套元素,则不执行任何操作。否则,两个处理器都将被调用。

// Handler of the parent element. Let's assume the nested element is a checkbox
function handleSingleSelection(e) {
    if(e.target.type !== 'checkbox') {
        // We do not do anything from the 
        // parent handler if the target is a checkbox ( our nested element)
        // Note that the target will always be the nested element
        dispatch(lineSelectionSingle({ line }))
    }
}

1
这个答案可能不适用于每个人,但对我来说很有用!+1 - Roland
1
哇塞,非常感谢!!Typescript 的补充: if ((e.target as HTMLInputElement).type !== "checkbox") {/*处理程序*/} - Anji

1
您可以通过检查事件的目标来避免事件冒泡。
例如,如果您在处理点击事件的 div 元素中嵌套了输入框,并且当单击输入框时您不想处理它时,您可以将 event.target 传递到处理程序中,并根据目标的属性判断是否应执行处理程序。
例如,您可以检查 if (target.localName === "input") { return}
因此,这是一种“避免”处理程序执行的方法。

1
我在使用 event.stopPropagation() 的时候遇到了问题。 如果你也有同样的问题,请尝试将其移动到单击处理程序函数的顶部,这是我所需要的停止事件冒泡的方法。示例函数:
  toggleFilter(e) {
    e.stopPropagation(); // If moved to the end of the function, will not work
    let target = e.target;
    let i = 10; // Sanity breaker

    while(true) {
      if (--i === 0) { return; }
      if (target.classList.contains("filter")) {
        target.classList.toggle("active");
        break;
      }
      target = target.parentNode;
    }
  }

0
另一种解决这个问题的方法是在子元素 (您的
  • 标签) 上设置 onMouseEnter 和 onMouseLeave 事件。每当鼠标悬停在子元素上时,您可以设置一个特定的状态,该状态将阻止父元素执行 onClick 函数内容。类似于:
    class List extends React.Component {
      constructor(props) {
        super(props)
        this.state.overLi = false
      }
    
      handleClick() {
        // do something
      }
    
      render() {
        return (
          <ul
            onClick={(e) => {
              if (!this.state.overLi) {
                console.log("parent")
                this.handleClick()
              }
            }}
          >
            <li
              onClick={(e) => {
                console.log("child")
                // prevent default? prevent propagation?
                this.handleClick()
              }}
              onMouseEnter={() =>
                this.setState({
                  overLi: true,
                })
              }
              onMouseLeave={() =>
                this.setState({
                  overLi: false,
                })}
            ></li>
          </ul>
        )
      }
    }
    

    我已经搜索了很多,但是没有使用e.stopPropagation()实现我的上下文的任何解决方案。


  • -4

    现在的做法更加简单,可以帮助您节省时间!只需将事件传递给原始点击处理程序并调用 preventDefault();

    clickHandler(e){
        e.preventDefault();
        //Your functionality here
    }
    

    3
    我测试过了,这并不能用于点击事件的传播。据我所知,如果你想要为链接添加一个 onClick 处理程序来停止基本链接功能,那么这会很有用,但它并不能用于事件冒泡。 - Joel Peltonen

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接