如何在 React 中处理父级的 mouseMove 事件?

Posted

技术标签:

【中文标题】如何在 React 中处理父级的 mouseMove 事件?【英文标题】:How to process mouseMove event in parent in React? 【发布时间】:2019-11-11 08:35:46 【问题描述】:

我正在尝试在 React 中实现拖放并使用 SVG 元素。问题是如果用户移动鼠标过快,mouseMove 不会被触发。它基本上经常失去拖动。为了解决这个问题,我认为我需要处理父级中的mouseMove,但不确定如何使用 React 来执行此操作。我尝试了几种不同的方法都无济于事。

我使用 ref 在父级上尝试了addEventListener('mousemove', ...),但问题是clientX 与当前组件的坐标系不同。此外,事件处理程序无权访问组件的任何状态(带有箭头函数的事件)。它维护对任何状态的过时引用。

我尝试在父级的context 中设置clientXclientY,然后将其从DragMe 组件中拉入,但由于某种奇怪的原因,第一次总是undefined我给它一个默认值。

这是我正在使用的代码:

    const DragMe = ( x = 50, y = 50, r = 10 ) => 
      const [dragging, setDragging] = useState(false)
      const [coord, setCoord] = useState( x, y )
      const [offset, setOffset] = useState( x: 0, y: 0 )
      const [origin, setOrigin] = useState( x: 0, y: 0 )

      const xPos = coord.x + offset.x
      const yPos = coord.y + offset.y

      const transform = `translate($xPos, $yPos)`

      const fill = dragging ? 'red' : 'green'
      const stroke = 'black'

      const handleMouseDown = e => 
        setDragging(true)
        setOrigin( x: e.clientX, y: e.clientY )
      

      const handleMouseMove = e => 
        if (!dragging)  return 
        setOffset(
          x: e.clientX - origin.x,
          y: e.clientY - origin.y,
        )
      

      const handleMouseUp = e => 
        setDragging(false)
        setCoord( x: xPos, y: yPos )
        setOrigin( x: 0, y: 0 )
        setOffset( x: 0, y: 0 )
      

      return (
        <svg style= userSelect: 'none' 
          onMouseDown=handleMouseDown
          onMouseUp=handleMouseUp
          onMouseMove=handleMouseMove
          onMouseLeave=handleMouseUp
        >
          <circle transform=transform cx="0" cy="0" r=r fill=fill stroke=stroke />
        </svg>
      )
    

【问题讨论】:

尝试添加 e.preventDefault();在每个事件方法中 也尝试添加e.stopPropagation() 我最初尝试过,但问题不是某处的其他事件。问题是 mousemove 仅在鼠标仍在对象上方时才会发出。当鼠标移动过快时,光标将在其位置更新之前离开对象。 【参考方案1】:

经过大量实验,我能够addEventListener 到父画布。我发现我需要useRef 才能让mousemove 处理程序看到当前状态。我之前遇到的问题是 handleParentMouseMove 处理程序对状态的引用过时并且从未见过 startDragPos

这是我想出的解决方案。如果有人知道清理此问题的方法,将不胜感激。

    const DragMe = ( x = 50, y = 50, r = 10, stroke = 'black' ) => 
      // `mousemove` will not generate events if the user moves the mouse too fast
      // because the `mousemove` only gets sent when the mouse is still over the object.
      // To work around this issue, we `addEventListener` to the parent canvas.
      const canvasRef = useContext(CanvasContext)

      const [dragging, setDragging] = useState(false)

      // Original position independent of any dragging.  Updated when done dragging.
      const [originalCoord, setOriginalCoord] = useState( x, y )

      // The distance the mouse has moved since `mousedown`.
      const [delta, setDelta] = useState( x: 0, y: 0 )

      // Store startDragPos in a `ref` so handlers always have the latest value.
      const startDragPos = useRef( x: 0, y: 0 )

      // The current object position is the original starting position + the distance
      // the mouse has moved since the start of the drag.
      const xPos = originalCoord.x + delta.x
      const yPos = originalCoord.y + delta.y
      const transform = `translate($xPos, $yPos)`

      // `useCallback` is needed because `removeEventListener`` requires the handler
      // to be the same as `addEventListener`.  Without `useCallback` React will
      // create a new handler each render.
      const handleParentMouseMove = useCallback(e => 
        setDelta(
          x: e.clientX - startDragPos.current.x,
          y: e.clientY - startDragPos.current.y,
        )
      , [])

      const handleMouseDown = e => 
        setDragging(true)
        startDragPos.current =  x: e.clientX, y: e.clientY 
        canvasRef.current.addEventListener('mousemove', handleParentMouseMove)
      

      const handleMouseUp = e => 
        setDragging(false)
        setOriginalCoord( x: xPos, y: yPos )
        startDragPos.current =  x: 0, y: 0 
        setDelta( x: 0, y: 0 )
        canvasRef.current.removeEventListener('mousemove', handleParentMouseMove)
      

      const fill = dragging ? 'red' : 'green'

      return (
        <svg style= userSelect: 'none' 
          onMouseDown=handleMouseDown
          onMouseUp=handleMouseUp
        >
          <circle transform=transform cx="0" cy="0" r=r fill=fill stroke=stroke />
        </svg>
      )
    

【讨论】:

【参考方案2】:

只是为了隔离在这里工作的代码: 首先在render() 函数中弄清楚你的***父级是什么,你可能有这样的东西:

render() 
  return (
    <div ref=(parent_div) =>  this.parent_div = parent_div > 
      // other div stuff 
    </div>
  )


然后,使用上面的 ref 分配,继续为它分配事件侦听器,如下所示:

this.parent_div.addEventListener('mousemove', function (event) 
  console.log(event.clientX)

【讨论】:

以上是关于如何在 React 中处理父级的 mouseMove 事件?的主要内容,如果未能解决你的问题,请参考以下文章

在 React 中通过更改父级的道具来更新子组件

我可以使用react中的useEffect挂钩从子级设置父级的状态

javascript父级鼠标移入移出事件中的子集影响父级的处理方法

React 事件机制

将元素从一个父级移动到另一个父级的反应动画

如何在 react.js 中跨子组件持久化数据或状态?