【问题标题】:How to handle React Svg Drag and Drop with React Hooks如何使用 React Hooks 处理 React Svg 拖放
【发布时间】:2019-04-26 17:41:18
【问题描述】:

我正在尝试在 SVG 形状上实现拖放。我成功地使它与使用类组件一起工作。这是代码沙箱的链接:https://codesandbox.io/s/qv81pq1roq

但是现在我想通过使用带有自定义 Hook 的新 React API 来提取这个逻辑,该 Hook 能够将此功能添加到功能组件中。我尝试了很多东西,但没有任何效果。这是我最后一次尝试:

https://codesandbox.io/s/2x2850vjk0

我怀疑我添加和删除事件监听器的方式有些问题......所以这是我的问题:

你认为将这个 DnD SVG 逻辑放到自定义 Hook 中是否可行?如果是,你知道我做错了什么吗?

【问题讨论】:

    标签: javascript reactjs svg drag-and-drop react-hooks


    【解决方案1】:

    我在这里修复了示例 - https://codesandbox.io/s/2w0oy6qnvn

    你的钩子示例中有很多问题:

    1. setPositionsetState 不同。它不会进行浅合并,而是将整个对象替换为新值,因此您必须使用 Object.assign() 或扩展运算符与先前的值合并。此外,setPosition() 钩子接受一个回调值,该值提供先前的状态值作为第一个参数,如果您需要在设置新值时引用它。

    2. 与类不同,handleMouseMove 函数在每次渲染中都会重新创建,因此在调用 document.addEventListener('mousemove', handleMouseMove) 时,document.removeEventListener('mousemove', handleMouseMove) 不再引用初始 handleMouseMove 值。解决方法是使用 useRef 创建一个在组件的整个生命周期中持续存在的对象,非常适合保留对函数的引用。

    3. handleMouseDown 中的事件参数和您在setPosition 中引用的事件参数不一样。因为 React 使用事件池并重用事件,setPosition 中的事件可能已经不同于传递给 handleMouseDown 的事件。解决这个问题的方法是先获取pageXpageY 的值,这样在setPosition 内就不需要依赖事件对象了。

    我在下面的代码中注释了你需要注意的部分。

    const Circle = () => {
      const [position, setPosition] = React.useState({
        x: 50,
        y: 50,
        coords: {},
      });
      
      // Use useRef to create the function once and hold a reference to it.
      const handleMouseMove = React.useRef(e => {
        setPosition(position => {
          const xDiff = position.coords.x - e.pageX;
          const yDiff = position.coords.y - e.pageY;
          return {
            x: position.x - xDiff,
            y: position.y - yDiff,
            coords: {
              x: e.pageX,
              y: e.pageY,
            },
          };
        });
      });
    
      const handleMouseDown = e => {
        // Save the values of pageX and pageY and use it within setPosition.
        const pageX = e.pageX; 
        const pageY = e.pageY;
        setPosition(position => Object.assign({}, position, {
          coords: {
            x: pageX,
            y: pageY,
          },
        }));
        document.addEventListener('mousemove', handleMouseMove.current);
      };
    
      const handleMouseUp = () => {
        document.removeEventListener('mousemove', handleMouseMove.current);
        // Use Object.assign to do a shallow merge so as not to 
        // totally overwrite the other values in state.
        setPosition(position =>
          Object.assign({}, position, {
            coords: {},
          })
        );
      };
    
      return (
        <circle
          cx={position.x}
          cy={position.y}
          r={25}
          fill="black"
          stroke="black"
          strokeWidth="1"
          onMouseDown={handleMouseDown}
          onMouseUp={handleMouseUp}
        />
      );
    };
    
    const App = () => {
      return (
        <svg
          style={{
            border: '1px solid green',
            height: '200px',
            width: '100%',
          }}
        >
          <Circle />
        </svg>
      );
    };
    
    ReactDOM.render(<App />, document.querySelector('#app'));
    <script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>
    
    <div id="app"></div>

    【讨论】:

    • 非常感谢阳顺的完整回复!我不知道这种使用 React.useRef() 的方式也可以应用于函数,而不仅仅是 dom 节点……非常有趣。出于好奇,是否可以仅通过使用 javascript 来获取对函数的引用?最后,我能够将所有逻辑放到自定义 Hook 中。这是新的沙盒:codesandbox.io/s/50n0qoqrzn。对了,新的 Hook api 真的亮了!
    • 是的,您可以使用纯 JS 来获取函数引用,这就是您在类组件版本中所做的。在函数中,我不认为你可以用你拥有的东西来做,因为函数没有状态/实例,你需要将函数的引用存储在函数组件之外的某个地方。很高兴您喜欢新的钩子 API!我也喜欢(:
    • 我怎样才能对缩放行为做同样的事情?
    • 很好的答案阳顺!为了安全起见,我会添加一个额外的钩子: useEffect(() => () => document.removeEventListener('mousemove', handleMouseMove.current));基本上,您要确保从文档中删除事件侦听器,以防组件在拖动时被删除。
    【解决方案2】:

    这是一个通用的自定义钩子,我一直在使用它来注册 SVG 元素上的拖动事件。

    import { useState, useEffect, useCallback, useRef } from 'react'
    
    // You may need to edit this to serve your specific use case
    function getPos(e) {    
      return {
        x: e.pageX,
        y: e.pageY,
      }
    }
    
    // from https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state    
    function usePrevious(value) {
      const ref = useRef()
      useEffect(() => {
        ref.current = value
      })
      return ref.current
    }
    
    export function useDrag({ onDrag, onDragStart, onDragEnd }) {
      const [isDragging, setIsDragging] = useState(false)
    
      const handleMouseMove = useCallback(
        (e) => {
          onDrag(getPos(e))
        }, 
        [onDrag]
      )
    
      const handleMouseUp = useCallback(
        (e) => {
          onDragEnd(getPos(e))
          document.removeEventListener('mousemove', handleMouseMove);
          setIsDragging(false)
        }, 
        [onDragEnd, handleMouseMove]
      )
    
      const handleMouseDown = useCallback(
        (e) => {
          onDragStart(getPos(e))
          setIsDragging(true)
          document.addEventListener('mousemove', handleMouseMove)
        }, 
        [onDragStart, handleMouseMove]
      )
    
      const prevMouseMove = usePrevious(handleMouseMove)
    
      useEffect(
        () => {
          document.removeEventListener('mousemove', prevMouseMove);
          if(isDragging) {
            document.addEventListener('mousemove', handleMouseMove)
          }
        },
        [prevMouseMove, handleMouseMove, isDragging]
      )
    
      useEffect(
        () => {
          if (isDragging) {
            document.addEventListener('mouseup', handleMouseUp)
          }
          return () => document.removeEventListener('mouseup', handleMouseUp)
        },
        [isDragging, handleMouseUp]
      )
    
      return handleMouseDown
    }
    

    【讨论】:

    • Nathan,你为什么使用 usePrevious() 钩子,为什么只用于 mouseMove 而不是 mouseUp?顺便说一句,看起来 addEventLister() 和 removeEventlListener() 被多次调用。我在这里转载:codepen.io/markvital/pen/NWGaeRw
    猜你喜欢
    • 2020-11-07
    • 2020-12-23
    • 1970-01-01
    • 2021-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多