React onMouseEnter 和 onMouseLeave 的行为不一致

Eva*_*ler 3 javascript reactjs

我有一个 div,如果光标悬停在其上,则应该显示“HOVERING”,否则显示“NOT HOVERING”。由于某种原因,如果我慢慢地将每个 div 悬停在页面上,它的行为就会符合预期;但是,如果我在屏幕上快速移动光标,某些 div 就会被切换。意思是,当我的光标移动到 div 上时,它们将显示“NOT HOVERING”,当我的光标不在 div 上时,它们将显示“HOVERING”。

Chrome 和 Safari 中都会出现此错误。

沙盒:

https://codesandbox.io/s/ged-butterfly-r2g6x?file=/src/Geo.js

将光标快速移动到方框上以查看问题。

Dre*_*ese 6

问题

我认为您的实现的主要问题在于异步事件回调在事件循环中排队和处理的方式。我找不到有关处理事件回调延迟的任何硬细节,但如果您愿意深入研究,这里这里的文档可能会进一步阐明这个问题。

基本上这个问题有两个方面:

  1. 单个事件循环需要一分钟的持续时间来处理,即检测事件并将其添加到队列中。我怀疑鼠标移动得足够快,离开/离开屏幕或进入另一个未检测到的div。悬停时 div 的“跳跃”/“移动”也没有多大帮助。
  2. 组件逻辑假设所有事件都可以并且将会被检测到,并且简单地切换到先前的现有状态。一旦错过了一个事件,尽管切换会反转,这就是您看到的问题。即使在更新的沙箱中,这种延迟也可能导致其中一个元素“卡住”悬停

建议的解决方案

将鼠标移动事件侦听器添加到窗口对象,并检查鼠标移动事件目标是否包含在您的元素之一中。如果当前未悬停且元素包含事件目标,则设置isHoveredtrue,如果当前悬停且元素不包含事件目标,则设置isHoveredfalse。

这并不能完全替代附加到包含的 Enter/leave|over/out 事件侦听器,div因为我仍然能够重现边缘情况。我注意到当快速移动鼠标并离开窗口时,您的 UI 最容易受到此问题的影响。

结合 window 和 div 事件侦听器可以提供相当好的分辨率(尽管我仍然能够重现边缘情况,但这要困难得多)。似乎也有一点帮助的是没有为 div 定义匿名回调函数。

import React, { createRef } from "react";

export default class Geo extends React.Component {
  state = {
    isHovering: false
  };
  mouseMoveRef = createRef();

  componentDidMount() {
    window.addEventListener("mousemove", this.checkHover, true);
  }

  componentWillUnmount() {
    window.removeEventListener("mousemove", this.checkHover, true);
  }

  setHover = () => this.setState({ isHovering: true });
  setUnhover = () => this.setState({ isHovering: false });

  checkHover = e => {
    if (this.mouseMoveRef.current) {
      const { isHovering } = this.state;
      const mouseOver = this.mouseMoveRef.current.contains(e.target);
      if (!isHovering && mouseOver) {
        this.setHover();
      }

      if (isHovering && !mouseOver) {
        this.setUnhover();
      }
    }
  };

  render() {
    var textDisplay;

    if (this.state.isHovering) {
      textDisplay = <span>HOVERING</span>;
    } else {
      textDisplay = <h1>NOT HOVERING</h1>;
    }

    return (
      <div
        ref={this.mouseMoveRef}
        onMouseEnter={this.setHover}
        onMouseLeave={this.setUnhover}
        style={{ width: 300, height: 100, background: "green" }}
      >
        {textDisplay}
      </div>
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

编辑友好的chatterjee-xx2ms