일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- 자바스크립트 배열
- HTTP
- github
- IndexedDB
- Github Pages
- react
- slide
- React.js
- deploy
- 리액트배포
- js
- 비동기 함수
- 웹스토리지
- 배열
- 자바스크립트
- 슬라이더
- 인덱스된 데이터베이스
- 슬라이드
- 렌더링
- array
- 배너
- 리액트
- 배너 슬라이더
- banner
- 웹
- javascript
- dnd-kit
- 웹사이트
- 배포
- vite
- Today
- Total
Somedding
React와 TypeScript로 드래그 앤 드롭2(@dnd-kit) 본문
지난 구현에 이어서 리스트로 정렬 가능한 드래그 앤 드롭을 구현해보고자 한다.
지난 구현에 쓰인 훅인 useDraggable과 useDroppable은 드래그 앤 드롭이 가능하지만,
정렬이 되는 형태는 아니었다.
즉, 이번에 알아볼 라이브러리와 훅은 리스트에서 순서를 드래그 앤 드롭으로 변경 가능하도록 할 수 있다.
사용할 라이브러리는 @dnd-kit이며, @dnd-kit/sortable를 설치해야 한다.
https://docs.dndkit.com/presets/sortable
Sortable | @dnd-kit – Documentation
The sortable preset provides the building blocks to build sortable interfaces.
docs.dndkit.com
우선 React와 TypeScript가 완전히 설치되어 있다고 가정하고 진행할 예정이다.
설치
공식 문서에도 나와있다시피 다음 명령어를 사용해서 라이브러리를 설치하면 된다.
@dnd-kit/utilities는 설치할 필요가 없지만, 드래그하는 요소의 위치를 직접 변환해 줄 필요 없이 간단하게 가능하도록 돕는다.
npm install @dnd-kit/sortable @dnd-kit/utilities
혹은 @dnd-kit/core가 설치되어 있지 않다면 @dnd-kit/core도 같이 설치하면 된다.
npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities
적용
드래그 앤 드롭을 적용하기 위해 App.tsx를 포함하여 총 2개의 컴포넌트를 사용한다.
App.tsx에서는 드래그 앤 드롭이 가능한 영역을 지정하고, 드래그 앤 드롭이 이루어졌는지 확인한다.
SortableItem.tsx는 리스트의 각 요소를 구성하는 컴포넌트로, 각 요소가 드래그 앤 드롭이 가능하도록 설정한다.
대략적인 로직은 다음과 같다.
1. App.tsx에서 드래그 앤 드롭이 가능한 컨텍스트를 지정하고, 리스트를 만든다.
2. 리스트만큼 반복하여 SortableItem 컴포넌트를 넣는다.
3. SortableItem 컴포넌트에서 만들어진 드래그 가능한 요소를 사용자가 드래그한다.
4. 사용자가 드래그를 시작한 지점과 놓은 지점을 App.tsx에서 이벤트를 통해 알도록 한다.
5. 해당 이벤트를 통해 드래그를 시작한 지점의 인덱스를 드래그를 놓은 지점의 인덱스로 옮긴다.
다음은 App.tsx 코드이다.
import {
closestCenter,
DndContext,
DragEndEvent,
TouchSensor,
useSensor,
useSensors,
} from '@dnd-kit/core';
import {
verticalListSortingStrategy,
SortableContext,
} from '@dnd-kit/sortable';
import SortableItem from './SortableItem';
import { useState } from 'react';
const initialItems = [
'Item1',
'Item2',
'Item3',
'Item4',
'Item5',
'Item6',
'Item7',
'Item8',
'Item9',
];
const Home = () => {
const sensors = useSensors(useSensor(TouchSensor)); // 마우스 드래그만 허용
const [items, setItems] = useState<string[]>(initialItems);
const handleDragEnd = (event: DragEndEvent) => {
};
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
<SortableContext items={items} strategy={verticalListSortingStrategy}>
<ul className="flex flex-column gap-10">
{items.map((v) => (
<SortableItem key={v} id={v} />
))}
</ul>
</SortableContext>
</DndContext>
);
};
export default Home;
우선 DndContext는 드래그 앤 드롭을 전역적으로 관리하는 컨텍스트이다.
이 컨텍스트 내부에서 드래그할 때 발생하는 이벤트를 감지하고, 드래그 및 드롭 요소를 추적한다.
sensors는 드래그 앤 드롭이 가능한 도구를 지정할 수 있다.
useSensor 훅을 통해 각각의 도구를 설정하고, useSensors를 통해 설정한 도구를 지정한다.
가능한 sensor는 총 4가지가 있다.
1. pointer: 마우스 + 터치로 드래그 가능
2. mouse: 마우스만으로 드래그 가능
3. touch: 터치만으로 드래그 가능
4. keyboard: 키보드 입력만으로 드래그 가능
이 센서들을 지정하는 방법은 다음과 같다.
const mouseSensor = useSensor(MouseSensor);
const touchSensor = useSensor(TouchSensor);
const keyboardSensor = useSensor(KeyboardSensor);
const sensors = useSensors(
mouseSensor,
touchSensor,
keyboardSensor,
);
// or
const sensors = useSensors(
useSensor(MouseSensor),
useSensor(TouchSensor),
useSensor(KeyboardSensor),
);
collisionDetection은 드래그한 요소가 어디에 드롭될지를 결정하는 충돌 감지 방식을 설정한다.
즉, 충돌되는 부분을 통해 드롭될 위치를 정할 수 있다.
가능한 충돌 알고리즘은 총 3가지가 있다.
1. Rectangle intersection: 드래그 한 요소가 겹치는 곳에 드롭
2. Closest center: 중심점이 가장 가까운 곳에 드롭
3. Closest corners: 네 모서리 중 가장 가까운 곳에 드롭
일반적으로는 2번이 가장 많이 쓰인다고 한다.
자세한 알고리즘은 공식 문서를 통해 알 수 있다.
https://docs.dndkit.com/api-documentation/context-provider/collision-detection-algorithms
Collision detection algorithms | @dnd-kit – Documentation
If you're familiar with how 2D games are built, you may have come across the notion of collision detection algorithms. One of the simpler forms of collision detection is between two rectangles that are axis aligned — meaning rectangles that are not rotat
docs.dndkit.com
handleDragEnd 함수는 드래그가 완료될 때 호출되는 함수이다.
이 함수에서 active와 over를 통해 드래그가 시작되고 끝난 위치의 정보를 확인할 수 있다.
결국 active의 인덱스를 구해서 over의 인덱스에 끼워 넣으면 된다.
SortableContext는 DndContext와 비슷하게 정렬 가능한 리스트의 드래그 앤 드롭을 전역적으로 관리하는 컨텍스트이다.
이 컨텍스트 안에서 드래그 앤 드롭으로 순서를 변경할 수 있다.
strategy는 리스트의 정렬 방식을 결정한다.
즉, 수평, 수직 혹은 그리드 방식을 선택할 수 있다.
1. verticalListSortingStrategy: 수직 정렬
2. horizontalListSortingStrategy: 수평 정렬
3. rectSortingStrategy: 그리드 정렬
4. rectSwappingStrategy: 그리드 정렬이지만, 순서를 밀어내는 방식이 아니라 두 요소를 교체
결국 사용자가 SortableContext 내부의 SortableItem 컴포넌트들을 드래그 앤 드롭을 하면
handleDragEnd 함수를 통해 리스트의 순서를 바꿀 수 있게 된다.
다음은 SortableItem.tsx 코드이다.
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
interface SortableItemProps {
id: string;
}
const SortableItem = ({ id }: SortableItemProps) => {
// useSortable 훅을 사용하여 정렬된 리스트에서 드래그 앤 드랍 가능한 영역을 설정
// setNodeRef: 이 요소가 드래그 가능하도록 설정하는 ref
// transform: 현재 드래그 중인 요소의 위치 변환 값 ({ x, y } 형태)
// listeners: 마우스 또는 터치 이벤트를 감지하여 드래그를 활성화
// attributes: ARIA 속성(접근성 지원) 자동 적용
// id: 해당 요소의 고유 ID. 같은 ID를 가진 요소는 여러 개 있으면 안 됨.
// transition: 애니메이션을 부드럽게 적용하는 CSS transition 값
const { attributes, listeners, setNodeRef, transform, transition } =
useSortable({ id });
const style = {
// 드래그한 요소의 변형(이동) 값을 CSS transform 속성으로 변환하는 역할
transform: CSS.Transform.toString(transform),
transition,
};
return (
// 리스트 요소를 드래그 가능하게 설정
<li
ref={setNodeRef}
style={style}
{...attributes}
{...listeners}
>
{id}
</li>
);
};
export default SortableItem;
useSortable은 @dnd-kit에서 제공하는 훅으로, 정렬된 리스트에서 드래그 앤 드롭 가능한 영역을 설정한다.
useSortable은 id 값을 지정하여 요소를 식별한다.
이때 반환되는 객체에는 드래그 기능을 위해 필요한 속성들이 포함된다.
- attributes: ARIA 속성을 자동 적용한다.
- listeners: 마우스 또는 터치 이벤트를 감지하여 드래그를 활성화한다.
- setNodeRef: 이 요소가 드래그 가능하도록 ref를 설정한다.
- transform: 현재 드래그 중인 요소의 위치 반환 값이다.({ x, y } 형태)
- transition: 애니메이션을 부드럽게 적용하는 CSS transition 값이다.
리스트를 통해 드래그 앤 드롭을 할 수 있도록 설정했다.
사용자가 리스트 아이템을 드래그하면 순서가 변경되는 걸 볼 수 있다.
현재는 handleDragEnd 함수를 설정하지 않아서 드롭하면 되돌아간다.
이제 App.tsx의 handelDragEnd 코드이다.
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event;
if (!over) return; // over이 null일 수 있음
if (active.id !== over.id) {
// 리스트 순서 변경
setItems((prev) => {
const oldIdx = prev.indexOf(active.id.toString());
const newIdx = prev.indexOf(over.id.toString());
return arrayMove(prev, oldIdx, newIdx);
});
}
};
active와 over를 통해 리스트의 순서를 변경할 수 있다.
active를 통해 찾은 oldIdx와 over를 통해 찾은 newIdx를 가지고 순서를 변경할 수 있다.
이때 arrayMove는 @dnd-kit/sortable에서 제공하는 함수로 순서를 바꿔주는 역할을 한다.
이제 드래그 이후에도 리스트의 순서가 바뀐 채로 유지되는 것을 볼 수 있다.
글 내용 중, 잘못됐거나 더 알아야 하는 지식이 있다면 댓글로 남겨주시면 감사하겠습니다!
모두 좋은 하루 보내세요:)
'React' 카테고리의 다른 글
setState안에서 2차원 배열 prev 출력 문제(결국 크롬) (0) | 2025.05.02 |
---|---|
Nav를 통한 스크롤 이동과 react-intersection-observer를 통한 스크롤 이동의 충돌 문제 (0) | 2025.04.07 |
React와 TypeScript로 드래그 앤 드롭(@dnd-kit) (0) | 2025.03.20 |
useEffect는 어떤 점에서 비효율적인가? (0) | 2024.12.16 |
React, Typescript에서 ESLint, Prettier 설정하기 (1) | 2024.11.07 |