ReactJS钩子-拖放多个useState钩子和样式化的组件

时间:2019-05-06 23:21:09

标签: reactjs styled-components react-hooks

我对钩子还很陌生,我正在尝试实现一个拖放容器组件,该组件在整个鼠标移动过程中都处理onDragStart,onDrag和onDragEnd函数。我一直在尝试使用钩子复制此处找到的代码:https://medium.com/@crazypixel/mastering-drag-drop-with-reactjs-part-01-39bed3d40a03

我几乎可以使用下面的代码来工作。它使用样式化的组件进行动画处理。问题是,仅当您缓慢移动鼠标时它才起作用。如果您快速移动鼠标,则会将SVG或该div中包含的所有内容抛出屏幕。

我有一个看起来像

component.js文件
import React, { useState, useEffect, useCallback } from 'react';
import { Container } from './style'

const Draggable = ({children, onDragStart, onDrag, onDragEnd, xPixels, yPixels, radius}) => {
  const [isDragging, setIsDragging] = useState(false);
  const [original, setOriginal] = useState({
    x: 0,
    y: 0
  });
  const [translate, setTranslate] = useState({
    x: xPixels,
    y: yPixels
  });
  const [lastTranslate, setLastTranslate] = useState({
    x: xPixels,
    y: yPixels
  });

  useEffect(() =>{
    setTranslate({
      x: xPixels,
      y: yPixels
    });
    setLastTranslate({
      x: xPixels,
      y: yPixels
    })
  }, [xPixels, yPixels]);

  const handleMouseMove = useCallback(({ clientX, clientY }) => {

    if (!isDragging) {
      return;
    }
    setTranslate({
      x: clientX - original.x + lastTranslate.x,
      y: clientY - original.y + lastTranslate.y
    });
  }, [isDragging, original,  lastTranslate, translate]);



  const handleMouseUp = useCallback(() => {
    window.removeEventListener('mousemove', handleMouseMove);
    window.removeEventListener('mouseup', handleMouseUp);

    setOriginal({
      x:0,
      y:0
    });
    setLastTranslate({
      x: translate.x,
      y: translate.y
    });

    setIsDragging(false);
    if (onDragEnd) {
      onDragEnd();
    }

  }, [isDragging, translate, lastTranslate]);

  useEffect(() => {
    window.addEventListener('mousemove', handleMouseMove);
    window.addEventListener('mouseup', handleMouseUp);

    return () => {
      window.removeEventListener('mousemove', handleMouseMove);
      window.removeEventListener('mouseup', handleMouseUp)
    };
  }, [handleMouseMove, handleMouseUp]);

  const handleMouseDown = ({ clientX, clientY }) =>{

    if (onDragStart) {
      onDragStart();
    }
    setOriginal({
      x: clientX,
      y: clientY
    });
    setIsDragging(true);
  };

  return(
    <Container
      onMouseDown={handleMouseDown}
      x={translate.x}
      y={translate.y}
      {...{radius}}
      isDragging={isDragging}
    >
      {children}
    </Container>
  )
};

export default Draggable

样式化的组件文件styled.js如下所示:

import styled from 'styled-components/macro';

const Container = styled.div.attrs({
  style: ({x,y, radius}) => ({
    transform: `translate(${x - radius}px, ${y - radius}px)`
  })
})`
  //cursor: grab;
  position: absolute;

  ${({isDragging}) =>
    isDragging && `

    opacity: 0.8
    cursor: grabbing
  `}
`;

export {
  Container
}

因此,我最初从父级传入了初始值。我认为我没有正确处理useEffect / useState,并且获取信息的速度不够快。

如果有人可以帮助我解决该问题,我将非常感激。再次道歉,但是我对使用挂钩非常陌生。

谢谢:)

1 个答案:

答案 0 :(得分:2)

理想情况下,由于setState是异步的,因此您需要将所有状态都移到一个object中(如中间示例所示)。然后,您可以利用setState callback确保在调用event listener时,每个event callbacksetState所使用的值都是最新的。

我认为那篇中篇文章中的示例存在相同的跳跃问题(这可能是示例视频缓慢移动对象的原因),但是如果没有有效的示例,这很难说。也就是说,为了解决此问题,我删除了originalXoriginalYlastTranslateXlastTranslateY值,因为我们利用{{1 }}回调。

此外,我将setState / event listeners简化为:

  • callbacks =>鼠标左键按住mousedown true
  • isDragging =>鼠标移动通过mousemovetranslateX更新来更新translateYclientX
  • clientY =>鼠标左键释放将mouseup设置为false。

这可确保只有一个事件侦听器实际上在转换isDraggingx的值。

如果要利用此示例包含多个圆圈,则需要使用y并利用useRef移动选定的圆圈;但是,这超出了您原始问题的范围。

最后,我还通过将refs重组为一个styled-components并返回styled.div.data.attr属性和function而不是{具有style属性的CSS,该属性是返回object的{​​{1}}。

已弃用:

style

已更新:

function

工作示例

Edit Drag and Drop Example


组件/容器

CSS

组件/可拖动

styled.div.attrs({
  style: ({ x, y, radius }) => ({
    transform: `translate(${x - radius}px, ${y - radius}px)`
  })
})`