[POB#32] Assignment 8 : 모두컴퍼니 기업 과제. Drag and Drop 기능 구현 ( React )

Assignment 8 과제를 통해 얻은 지식 정리 글입니다.
과제 레파지토리 : https://github.com/6trillion/Assignmet8-TeamA

Drag and Drop 구현 ( React )

이전 글 VanillaJS로 구현한 Drag and Drop을 React로 구현해봤습니다.

사용한 이벤트는 같습니다. React에선 DOM을 직접 접근하는 것을 추천하지 않기 때문에 최소한으로 접근하면서 구현했습니다.

useRef

JS에선 querySelector, queryElementById 등으로 DOM에 접근하지만 React에선 특수한 경우에만 DOM에 직접 접근하기 때문에 useRef hook을 이용해 필요할 때만 접근합니다.

const ContainerRef = useRef();

return <Container ref={AreaRef}>...</Container>;

querySelectorAll과 같이 다수의 DOM을 저장하기 위해서 useRef의 초깃값을 배열로 선언합니다.

React에서 동일한 레이아웃은 거의 다 재사용을 하기 때문에 ref를 하위 컴포넌트로 전달해야 합니다. ref를 하위 컴포넌트로 전달할 땐 forwardRef로 하위 컴포넌트를 감싸줘야 부모 컴포넌트에서 ref를 사용할 수 있습니다.

const DraggableRef = useRef([]);

return (
    {TodoItems.map((item) => (
        <Todo key={item.id} todo={item}
            ref={(r) => DraggableRef() = r}
        />
    ))}
)
const Todo = ({ todo }, ref) => {
  return <Draggable ref={ref}>{todo.id}</Draggable>;
};

export default forwardRef(Todo);

onDragStart, onDragEed 이벤트

JS에선 드래그한 속성에 dragging이라는 class를 추가해 식별했지만, DOM을 직접 접근하지 않기 때문에 드래그한 속성의 todos만을 저장했습니다.

const handleDragStart = useCallback(
  (todo) => {
    setDragTodo(todo);
  },
  [setDragTodo]
);

const handleDragEnd = useCallback(() => {
  setDragTodo(null);
}, [setDragTodo]);

return (
  <Draggable
    draggable
    ref={ref}
    onDragStart={() => handleDragStart(todo)}
    onDragEnd={handleDragEnd}
  >
    {todo.id}
  </Draggable>
);

onDragOver 이벤트

DragOver 이벤트도 마찬가지입니다. appendChildinsertBefore를 사용하지 않기 때문에 element가 아닌 다른 속성의 값을 가져와 조작해야 합니다.
따라서 JS와는 다르게 지나고 있는 속성의 element가 아닌 속성의 indexafterIndex를 반환합니다.
afterIndex가 null 일 때, 가장 아래에 있어 index를 가져올 수 없을 때, 현재 드래그한 todo를 지우고 가장 뒤에 추가해 줍니다.
afterIndex가 특정 index를 가질 때, splice를 이용해 현재 todo를 지우고, 특정 index 뒤에 추가시킵니다.
마지막으로 TodoItemssetState 하여 갱신 시켜줍니다.

return <Container onDragOver={handleDragOver}>...</Container>;
const handleDragOver = useCallback(
  (e) => {
    e.preventDefault();
    const afterIndex = getDragAfterElement(e.clientY);

    if (dragTodo) {
      if (afterIndex === null) {
        const nextState = [...todos];
        const index = nextState.indexOf(dragTodo);
        nextState.splice(index, 1);
        nextState.push(dragTodo);
        // setState(nextState)로 state 갱신
      } else {
        const nextState = [...todos];
        const index = nextState.indexOf(dragTodo);
        nextState.splice(index, 1);
        nextState.splice(afterIndex, 0, dragTodo);
        // setState(nextState)로 state 갱신
      }
    }
  },
  [getDragAfterElement, dragTodo, dispatch, todos]
);

getDragAfterElement 함수

JS와 로직은 비슷하지만 element가 아닌 index를 반환하는 것에서 차이가 있습니다. 배열로 만든 useRef를 이용해 todo의 모든 DOM에 대해 y 값으로 현재 지나고 있는 속성들이 index를 반환합니다.

const getDragAfterElement = useCallback(y) => {
  const draggableElements = [...ListRef.current.filter((v) => v !== undefined)];

  return draggableElements.reduce(
    (closest, child, index) => {
      const box = child.getBoundingClientRect();
      const offset = y - box.top - box.height / 2;

      if (offset < 0 && offset > closest.offset) {
        return { offset: offset, element: child, index: index };
      } else {
        return closest;
      }
    },
    { offset: Number.NEGATIVE_INFINITY }
  ).index;
}, []);

좋은 웹페이지 즐겨찾기