Как работать с React Svg Drag and Drop с помощью React Hooks - PullRequest
0 голосов
/ 24 ноября 2018

Я пытаюсь реализовать Drag and Drop на фигуре SVG.Мне удалось заставить его работать с использованием Class Component.Вот ссылка на изолированную программную среду кода: https://codesandbox.io/s/qv81pq1roq

Но теперь я хотел бы извлечь эту логику, используя новый API React с пользовательским хуком, который сможет добавить эту функцию в функциональный компонент.Я перепробовал много вещей, но ничего не работает.Вот моя последняя попытка:

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

Я подозреваю что-то в связи с тем, как я добавляю и удаляю прослушиватель событий ... Итак, вот мои вопросы:

Как вы думаете, возможно ли применить эту DnG SVG-логику к пользовательскому хуку?Если это так, вы понимаете, что я делаю не так?

Ответы [ 2 ]

0 голосов
/ 01 мая 2019

Вот универсальный пользовательский хук, который я использовал для регистрации событий перетаскивания в 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
}
0 голосов
/ 25 ноября 2018

Я исправил пример здесь - https://codesandbox.io/s/2w0oy6qnvn

В вашем примере хуков было несколько проблем:

  1. setPosition отличается от setState,Он не выполняет поверхностное объединение, он заменяет весь объект новым значением, поэтому для объединения с предыдущим значением вам нужно использовать Object.assign() или оператор распространения.Кроме того, хук setPosition() принимает значение обратного вызова, которое предоставляет предыдущее значение состояния в качестве первого параметра, если вам нужно ссылаться на него при установке нового значения.

  2. В отличие от классов,Функция handleMouseMove воссоздается при каждом рендеринге, поэтому document.removeEventListener('mousemove', handleMouseMove) больше не ссылается на начальное значение handleMouseMove при вызове document.addEventListener('mousemove', handleMouseMove).Обходной путь для этого должен использовать useRef, который создает объект, который сохраняется в течение всего срока службы компонента, идеально подходит для сохранения ссылки на функции.

  3. Событиепараметр в handleMouseDown и тот, на который вы ссылаетесь в setPosition, не совпадают.Поскольку React использует пул событий и повторно использует события, событие в setPosition может уже отличаться от события, переданного в handleMouseDown.Чтобы обойти это, сначала нужно получить значения pageX и pageY, чтобы в 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>
...