refs 上的 addEventListener 反应显示出奇怪的行为

addEventListener on refs in react showing strange behaviour

我在父子关系中创建了 2 个组件。 第一个组件使用 refs,在 componentDidMount 中我在 DOM 节点上添加了一个点击事件监听器, 在子组件中,我还添加了一个 onclick 侦听器,但这次使用 DOM 元素上的道具。

但是现在当我点击子组件时,父组件的事件侦听器被调用,如果我使用 props 而不是 refs 在父组件上添加 onClick 一切正常。

所以,谁能告诉我为什么当我们使用 refs 添加事件侦听器时会显示此行为。

class SecondDiv extends React.Component {
  
  divClicked = (event) => {
    event.stopPropagation();
    console.log('Second Div clicked');
  }
  
  render() {
    return <div className="Div2" onClick={this.divClicked} >
      This is second div
    </div>
  }
}

class MovieItem extends React.Component {

   constructor(props) {
     super(props);
     this.node = React.createRef();
   }
  
  componentDidMount() {
    this.node.current.addEventListener('click', this.divClicked);
  }

  componentWillUnmount() {
    this.node.current.removeEventListener('click', this.divClicked);
  }
  
  divClicked = (event) => {
    event.stopPropagation();
    console.log('First div clicked');
  }

  render() {
    // 1st approach
    return <div ref={this.node} className="Div1" >
      <p>This is Div 1</p>
      <SecondDiv />
    </div>
    
    // 2nd approach
    // return <div ref={this.node} className="Div1" onClick={this.divClicked} >
    //   <p>This is Div 1</p>
    //   <SecondDiv />
    // </div>
  }

}

ReactDOM.render(<MovieItem />, document.getElementById("app"));
<div id="app"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Also on CodePen

这是因为 React 使用事件委托来提供综合事件处理程序,您在 DOM 事件处理程序上使用 stopPropagation。由于它使用的处理程序位于根元素上(在您的情况下为 id="app"),如果您使用 DOM 处理程序来阻止事件传播,React 不会看到它并且不会触发您的事件处理程序。

你的元素是这样的:

+−−−−−−−−−−−−−−−−−−−−−−+
|         #app         |
|  +−−−−−−−−−−−−−−−−−+ |
|  |     .Div1       | |
|  |  +−−−−−−−−−−−+  | |
|  |  |  .Div2    |  | |
|  |  +−−−−−−−−−−−+  | |
|  +−−−−−−−−−−−−−−−−−+ |
+−−−−−−−−−−−−−−−−−−−−−−+

React 的 DOM 处理程序在 #app 上。您的 DOM 处理程序在 .Div1 上。当您单击 .Div2 时,它会冒泡到 .Div1,您的 DOM 处理程序会看到它并停止它,因此它永远不会到达 #app。由于它没有达到 #appp,React 不会为 .Div2.

执行它的合成事件

一般来说,当您可以使用 React 事件处理程序时,不要使用 DOM 事件处理程序。但是,如果您需要使用 DOM 处理程序,并且您希望 React 的处理程序也 运行,请不要阻止传播。

这是在 DOM 处理程序中注释掉 stopPropagation 的示例:

class SecondDiv extends React.Component {
  
  divClicked = (event) => {
    event.stopPropagation();
    console.log('Second Div clicked');
  }
  
  render() {
    return <div className="Div2" onClick={this.divClicked} >
      This is second div
    </div>
  }
}

class MovieItem extends React.Component {

   constructor(props) {
     super(props);
     this.node = React.createRef();
   }
  
  componentDidMount() {
    this.node.current.addEventListener('click', this.divClicked);
  }

  componentWillUnmount() {
    this.node.current.removeEventListener('click', this.divClicked);
  }
  
  divClicked = (event) => {
    // event.stopPropagation(); // <============== Removed
    console.log('First div clicked');
  }

  render() {
    // 1st approach
    return <div ref={this.node} className="Div1" >
      <p>This is Div 1</p>
      <SecondDiv />
    </div>
    
    // 2nd approach
    // return <div ref={this.node} className="Div1" onClick={this.divClicked} >
    //   <p>This is Div 1</p>
    //   <SecondDiv />
    // </div>
  }

}

ReactDOM.render(<MovieItem />, document.getElementById("app"));
<div id="app"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

如您所见,它现在可以看到这两个事件,但正如您在评论中指出的那样,您看到它们的顺序很奇怪:

First div clicked
Second Div clicked

这又是因为 React 的点击处理程序在 #app 上,所以它不会触发 .Div2 的合成事件,直到 .Div1 的 DOM 事件已经被解雇了。