ReactJS钩子-使用多个useState钩子和样式化组件进行拖放

min*_*hah 1 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

Run Code Online (Sandbox Code Playgroud)

样式化的组件文件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
}

Run Code Online (Sandbox Code Playgroud)

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

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

谢谢 :)

Mat*_*tta 5

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

我认为该篇中篇文章中的示例具有相同的跳跃问题(这可能是示例视频缓慢移动对象的原因),但是如果没有有效的示例,很难说。这就是说,要解决这个问题,我删除了originalXoriginalYlastTranslateXlastTranslateY因为不是在需要的时候,因为我们利用价值setState的回调。

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

  • mousedown=>鼠标左键按住设置为isDraggingtrue
  • mousemove=>鼠标移动更新translateX以及translateY通过clientXclientY更新
  • mouseup=>鼠标左键释放设置isDragging为false。

这将确保只有一个事件侦听器实际上是转化xy价值。

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

最后,我还修正了styled-components通过重组弃用问题styled.div.data.attr是一个function一个返回style与财产CSS,而不是一个object一个style是一个属性function是回报CSS

不推荐使用:

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

更新:

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

工作示例

编辑拖放示例


零件/圆

import styled from "styled-components";

const Circle = styled.div.attrs(({ x, y, radius }) => ({
  style: {
    transform: `translate(${x - radius}px, ${y - radius}px)`
  }
}))`
  cursor: grab;
  position: absolute;
  width: 25px;
  height: 25px;
  background-color: red;
  border-radius: 50%;

  ${({ isDragging }) =>
    isDragging &&
    `
    opacity: 0.8;
    cursor: grabbing;
  `}
`;

export default Circle;
Run Code Online (Sandbox Code Playgroud)

组件/可拖动

import React, { useState, useEffect, useCallback } from "react";
import PropTypes from "prop-types";
import Circle from "../Circle";

const Draggable = ({ position, radius }) => {
  const [state, setState] = useState({
    isDragging: false,
    translateX: position.x,
    translateY: position.y
  });

  // mouse move
  const handleMouseMove = useCallback(
    ({ clientX, clientY }) => {
      if (state.isDragging) {
        setState(prevState => ({
          ...prevState,
          translateX: clientX,
          translateY: clientY
        }));
      }
    },
    [state.isDragging]
  );

  // mouse left click release
  const handleMouseUp = useCallback(() => {
    if (state.isDragging) {
      setState(prevState => ({
        ...prevState,
        isDragging: false
      }));
    }
  }, [state.isDragging]);

  // mouse left click hold
  const handleMouseDown = useCallback(() => {
    setState(prevState => ({
      ...prevState,
      isDragging: true
    }));
  }, []);

  // adding/cleaning up mouse event listeners
  useEffect(() => {
    window.addEventListener("mousemove", handleMouseMove);
    window.addEventListener("mouseup", handleMouseUp);

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

  return (
    <Circle
      isDragging={state.isDragging}
      onMouseDown={handleMouseDown}
      radius={radius}
      x={state.translateX}
      y={state.translateY}
    />
  );
};

// prop type schema
Draggable.propTypes = {
  position: PropTypes.shape({
    x: PropTypes.number,
    y: PropTypes.number
  }),
  radius: PropTypes.number
};

// default props if none are supplied
Draggable.defaultProps = {
  position: {
    x: 20,
    y: 20
  },
  radius: 10,
};

export default Draggable;
Run Code Online (Sandbox Code Playgroud)