React와 TypeScript로 드래그 앤 드롭(@dnd-kit)
웹에서 드래그 앤 드롭을 구현해보고자 한다.
(타이니팜 리마스터 게임을 하다가 갑자기 웹에서도 드래그 앤 드롭을 구현해보고 싶어 졌다.)
우선 Vite를 통해 리액트 개발을 진행할 것이다.
드래그 앤 드롭은 라이브러리로 잘 만들어져 있어서 해당 라이브러리를 사용하는 방법을 작성해보고자 한다.
다음은 이번에 사용하게 될 드래그 앤 드롭 라이브러리이다.
Overview | @dnd-kit – Documentation
@dnd-kit – A lightweight, modular, performant, accessible and extensible drag & drop toolkit for React.
docs.dndkit.com
우선 React와 TypeScript가 완전히 설치되어 있다고 가정하고 진행할 예정이다.
설치
공식 문서에도 나와있다시피 다음 명령어를 사용해서 라이브러리를 설치하면 된다.
npm install @dnd-kit/core
적용
드래그 앤 드롭을 적용하기 위해 App.tsx를 포함하여 총 3개의 컴포넌트를 사용한다.
App.tsx에서는 드래그 앤 드롭이 가능한 영역을 지정하고, 드래그 앤 드롭이 이루어졌는지 확인한다.
Draggable.tsx에서는 드래그가 가능한 요소를 생성한다.
Droppable.tsx에서는 드롭이 가능한 요소를 생성한다.
대략적인 로직은 다음과 같다.
1. App.tsx에서 드래그 앤 드롭이 가능한 컨텍스트를 지정하고, Draggable, Droppable 컴포넌트를 넣는다.
2. Draggable.tsx에서 만들어진 드래그 가능한 요소를 사용자가 드래그한다.
3. 사용자가 드래그를 놓은 지점을 App.tsx에서 이벤트를 통해 알도록 한다.
4. 해당 이벤트를 통해 드래그를 놓은 지점이 Droppable.tsx에서 만들어진 드롭 가능한 요소에 포함되어 있는지 확인한다.
5. 만약 포함된다면 정상적으로 드롭이 됐다고 판단한다.
다음은 App.tsx 코드이다.
import { useState } from 'react';
import { DndContext, DragEndEvent } from '@dnd-kit/core';
import { Droppable } from './Droppable';
import { Draggable } from './Draggable';
export const App = () => {
const [isDropped, setIsDropped] = useState<boolean>(false); // 정상적으로 드롭이 됐는지 확인
const draggableMarkup = <Draggable>Drag me</Draggable>;
const handleDragEnd = (event: DragEndEvent): void => {
// 드롭 가능한 영역
if (event.over?.id === 'droppable') {
setIsDropped(true);
}
};
return (
<DndContext onDragEnd={handleDragEnd}>
{!isDropped ? draggableMarkup : 'Drop Done'}
<Droppable>{isDropped ? draggableMarkup : 'Drop here'}</Droppable>
</DndContext>
);
};
우선 DndContext는 드래그 앤 드롭을 전역적으로 관리하는 컨텍스트이다.
이 컨텍스트 내부에서 드래그할 때 발생하는 이벤트를 감지하고, 드래그 및 드롭 요소를 추적한다.
handleDragEnd 함수는 드래그가 완료될 때 호출되는 함수이다.
이 함수에서 event.over를 통해 드롭된 위치의 정보를 확인할 수 있다.
드래그 앤 드롭을 진행할 때 우리는 id를 통해 요소들을 판별한다.
따라서 event.over를 통해 드롭된 위치의 "id가 드롭 가능한 영역의 id"와 일치하는지 확인할 수 있다.
결국 드롭 가능한 영역에 사용자가 드래그를 해서 놓게 되면,
isDropped를 true로 만들어서 사용자가 해당 영역에 드롭했다는 것을 알 수 있다.
다음은 Draggable.tsx 코드이다.
import { useDraggable } from '@dnd-kit/core';
import { ReactNode } from 'react';
interface DraggableProps {
children: ReactNode;
}
export const Draggable = ({ children }: DraggableProps) => {
// useDraggable 훅을 사용하여 드래그 가능한 요소 생성
// setNodeRef: 이 요소가 드래그 가능하도록 설정하는 ref
// transform: 현재 드래그 중인 요소의 위치 변환 값 ({ x, y } 형태)
// listeners: 마우스 또는 터치 이벤트를 감지하여 드래그를 활성화
// attributes: ARIA 속성(접근성 지원) 자동 적용
// id: 해당 요소의 고유 ID. 같은 ID를 가진 요소는 여러 개 있으면 안 됨.
const { attributes, listeners, setNodeRef, transform } = useDraggable({
id: 'draggable',
});
// 드래그 중일 때 요소의 위치를 변환하는 스타일 설정
// 드래그가 없으면 기본 스타일 유지
const style = transform
? {
transform: `translate3d(${transform.x}px, ${transform.y}px, 0)`,
}
: undefined;
return (
// 버튼 요소를 드래그 가능하게 설정
<button ref={setNodeRef} style={style} {...listeners} {...attributes}>
{children}
</button>
);
};
useDraggable은 @dnd-kit에서 제공하는 훅으로, 드래그 가능한 요소를 생성할 때 사용한다.
useDraggable은 id 값을 지정하여 요소를 식별한다.
이때 반환되는 객체에는 드래그 기능을 위해 필요한 속성들이 포함된다.
- attributes: ARIA 속성을 자동 적용한다.
- listeners: 마우스 또는 터치 이벤트를 감지하여 드래그를 활성화한다.
- setNodeRef: 이 요소가 드래그 가능하도록 ref를 설정한다.
- transform: 현재 드래그 중인 요소의 위치 반환 값이다.({ x, y } 형태)
현재는 버튼을 드래그 가능한 요소로 지정했다.
사용자가 버튼을 드래그하면 버튼이 따라 움직이는 것을 확인할 수 있다.
다음은 Droppable.tsx 코드이다.
import { useDroppable } from '@dnd-kit/core';
import { ReactNode } from 'react';
interface DroppableProps {
children: ReactNode;
}
export const Droppable = ({ children }: DroppableProps) => {
// useDroppable 훅을 사용하여 드랍 가능한 영역을 설정
// isOver: 사용자가 드래그한 요소가 드랍 가능한 영역 위에 있을 때 true로 변경
// setNodeRef: 이 요소가 드랍 가능하도록 설정하는 ref
// id: 드랍 영역의 고유 ID (드래그된 요소가 이 ID를 가진 곳으로 드랍 가능)
const { isOver, setNodeRef } = useDroppable({
id: 'droppable',
});
// 드랍 영역 위에 드래그된 요소가 올라와 있을 때 색상을 변경
const style = {
color: isOver ? 'green' : undefined,
};
return (
// 드랍 가능한 영역을 설정하는 div
<div ref={setNodeRef} style={style} className="absolute top-2 left-2">
{children}
</div>
);
};
useDroppable은 useDraggable처럼 @dnd-kit에서 제공하는 훅으로, 드롭 가능한 요소를 생성할 때 사용한다.
useDroppable도 역시 id 값을 지정하여 요소를 식별한다.
이때 반환되는 객체에는 드롭 기능을 위해 필요한 속성들이 포함된다.
- isOver: 사용자가 드래그한 요소가 드롭 가능한 영역 위에 있을 때 true로 변경
- setNodeRef: 이 요소가 드롭 가능하도록 ref를 설정한다.
현재는 사용자가 드래그 가능한 버튼을 드롭 가능한 영역에 드래그하면
드롭 가능한 영역의 글이 초록색으로 바뀌는 것을 볼 수 있다.
이제 간단한 드래그 앤 드롭은 완성이 됐다.
드래그 가능한 요소가 드롭 가능한 요소에 드롭을 하게 되면 다음과 같이 드롭이 된다.
이렇게 @dnd-kit을 이용하여 간단하게 드래그 앤 드롭 예제를 알아봤다.
다음에는 @dnd-ket/sortable이라는 라이브러리를 통해 드래그 앤 드롭으로 정렬할 수 있도록 할 것이다.
글 내용 중, 잘못됐거나 더 알아야 하는 지식이 있다면 댓글로 남겨주시면 감사하겠습니다!
모두 좋은 하루 보내세요:)