首页 > 解决方案 > 如何使用 React Hooks 处理 React Svg 拖放

问题描述

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

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

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

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

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

标签: javascriptreactjssvgdrag-and-dropreact-hooks

解决方案


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

您的钩子示例中有许多问题:

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

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

  3. 中的事件参数handleMouseDown和您引用的事件参数setPosition不同。因为 React 使用事件池并重用事件,所以 in 中的事件setPosition可能已经与传入的事件不同handleMouseDown。解决这个问题的方法是首先获取 和 的值,pageX以便pageYsetPosition其中不需要依赖事件对象。

我用您需要注意的部分注释了下面的代码。

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>


推荐阅读