React에서는 react-dnd 라이브러리를 사용하여 드래그 앤 드롭 기능을 간편하게 구현할 수 있다.
설치
먼저, react-dnd와 react-dnd-html5-backend를 설치한다. react-dnd-html5-backend는 HTML5의 드래그 앤 드롭 API를 사용하는 기본 백엔드이다.
npm i react-dnd react-dnd-html5-backend
npm i -D @types/react-dnd
React DnD에서 백엔드는 드래그 앤 드롭 이벤트를 처리하고, 드래그된 아이템의 상태를 관리하는 시스템이다. "백엔드"라는 용어는 여기서 드래그 앤 드롭 기능을 지원하는 '기술적 메커니즘' 또는 '엔진'을 의미한다. 실제 서버 백엔드와 혼동할 수 있지만, 문맥상 이 백엔드는 클라이언트 측에서 동작하는 드래그 앤 드롭의 내부 동작 방식을 의미한다.
다양한 백엔드를 사용할 수 있지만, 가장 흔히 사용되는 백엔드는 HTML5의 드래그 앤 드롭 API를 사용하는 HTML5 Backend이다. 이 백엔드는 마우스와 터치 이벤트를 처리하고, 브라우저의 기본 드래그 앤 드롭 기능을 활용한다. 다른 백엔드를 사용할 수도 있는데, 예를 들어 터치 장치나 커스텀 렌더링 환경을 지원하는 백엔드가 있다.
기본 설정
React DnD를 사용하려면 애플리케이션의 루트 컴포넌트를 DndProvider
로 감싸야한다. DndProvider는 드래그 앤 드롭 콘텍스트를 제공한다.
import React from 'react';
import ReactDOM from 'react-dom';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import App from './App';
ReactDOM.render(
<DndProvider backend={HTML5Backend}>
<App />
</DndProvider>,
document.getElementById('root')
);
useDrag
useDrag 훅은 요소를 드래그 가능하게 만든다. useDrag 훅을 사용하여 드래그 소스의 설정을 정의할 수 있다.
import React from 'react';
import { useDrag } from 'react-dnd';
// 드래그 가능한 아이템 타입 정의
const ItemTypes = {
CARD: 'card',
};
// Card 컴포넌트는 드래그 가능한 카드 아이템을 렌더링한다
const Card = ({ id, text }) => {
// useDrag 훅을 사용하여 드래그 상태와 드래그 참조 설정
const [{ isDragging }, drag] = useDrag({
type: ItemTypes.CARD, // 드래그 아이템의 타입 설정
item: { id }, // 드래그 시 전송될 아이템 데이터 설정
collect: (monitor) => ({
isDragging: monitor.isDragging(), // 드래그 중인지 여부를 수집
}),
});
return (
// ref={drag}는 이 div 요소를 드래그 소스로 설정한다
<div
ref={drag}
style={{
opacity: isDragging ? 0.5 : 1, // 드래그 중이면 반투명하게 표시
cursor: 'move', // 커서를 이동 아이콘으로 변경
}}
>
{text} {/* 카드 텍스트를 표시 */}
</div>
);
};
export default Card;
useDrop
useDrop 훅은 요소가 드래그된 아이템을 받을 수 있게 만든다. useDrop훅을 사용하여 드롭 대상의 설정을 정의할 수 있다.
import React from 'react';
import { useDrop } from 'react-dnd';
// 드롭 가능한 아이템 타입 정의
const ItemTypes = {
CARD: 'card',
};
// DropArea 컴포넌트는 드래그 가능한 카드 아이템을 드롭할 수 있는 영역을 렌더링한다
const DropArea = ({ onDrop }) => {
// useDrop 훅을 사용하여 드롭 상태와 드롭 참조를 설정한다
const [{ isOver }, drop] = useDrop({
accept: ItemTypes.CARD, // 이 드롭 영역에서 받아들일 아이템 타입을 설정
drop: (item) => onDrop(item.id), // 아이템이 드롭되었을 때 호출될 함수, 아이템의 id를 인자로 전달
collect: (monitor) => ({
isOver: monitor.isOver(), // 드롭 영역 위에 드래그 중인지 여부를 수집
}),
});
return (
// ref={drop}는 이 div 요소를 드롭 영역으로 설정한다
<div
ref={drop}
style={{
height: '100px', // 드롭 영역의 높이 설정
width: '100px', // 드롭 영역의 너비 설정
backgroundColor: isOver ? 'lightgreen' : 'lightgray', // 드래그 중일 때와 아닐 때의 배경색 설정
}}
>
Drop here {/* 드롭 영역의 텍스트 표시 */}
</div>
);
};
export default DropArea;
드래그 앤 드롭 기능 통합
위의 두 컴포넌트를 통합하여 드래그 앤 드롭 기능을 완성한다.
import React, { useState } from 'react';
import Card from './Card';
import DropArea from './DropArea';
const App = () => {
const [droppedItem, setDroppedItem] = useState(null);
const handleDrop = (id) => {
setDroppedItem(id);
};
return (
<div>
<Card id="1" text="Draggable Card" />
<DropArea onDrop={handleDrop} />
{droppedItem && <p>Dropped item: {droppedItem}</p>}
</div>
);
};
export default App;
useDragLayer
useDragLayer 훅은 커스텀 드래그 레이어를 만들 때 사용된다. 드래그 중인 아이템의 커스텀 미리 보기를 렌더링 하는 데 사용된다.
import React from 'react';
import { useDragLayer } from 'react-dnd';
// CustomDragLayer 컴포넌트는 커스텀 드래그 미리보기를 렌더링한다
const CustomDragLayer = () => {
// useDragLayer 훅을 사용하여 드래그 상태와 아이템 위치를 설정한다
const { item, isDragging, currentOffset } = useDragLayer((monitor) => ({
item: monitor.getItem(), // 드래그 중인 아이템을 가져온다
isDragging: monitor.isDragging(), // 드래그 중인지 여부를 가져온다
currentOffset: monitor.getClientOffset(), // 드래그 중인 아이템의 현재 좌표를 가져온다
}));
// 드래그 중이 아니면 아무것도 렌더링하지 않는다
if (!isDragging) {
return null;
}
// 커스텀 드래그 레이어의 스타일을 설정한다
const layerStyles = {
position: 'fixed', // 고정 위치
pointerEvents: 'none', // 포인터 이벤트 비활성화
zIndex: 100, // z-index 설정
left: currentOffset.x, // 현재 좌표의 x값 설정
top: currentOffset.y, // 현재 좌표의 y값 설정
};
return (
// 커스텀 드래그 레이어를 렌더링한다
<div style={layerStyles}>
<div style={{ transform: 'rotate(5deg)' }}>
{item.text} {/* 드래그 중인 아이템의 텍스트 */}
</div>
</div>
);
};
export default CustomDragLayer;
useDragDropManager
useDragDropManager 훅은 드래그 앤 드롭 매니저에 접근할 수 있게 한다. 매니저를 통해 드래그 앤 드롭 시스템의 상태와 설정을 제어할 수 있다.
import React from 'react';
import { useDragDropManager } from 'react-dnd';
// DragDropStatus 컴포넌트는 현재 드래그 상태를 표시한다
const DragDropStatus = () => {
// useDragDropManager 훅을 사용하여 DragDropManager 인스턴스를 가져온다
const dragDropManager = useDragDropManager();
// DragDropManager의 모니터를 가져온다
const monitor = dragDropManager.getMonitor();
// 현재 드래그 중인지 여부를 가져온다
const isDragging = monitor.isDragging();
return (
<div>
{/* 현재 드래그 상태에 따라 메시지를 표시한다 */}
{isDragging ? 'An item is being dragged' : 'No items are being dragged'}
</div>
);
};
export default DragDropStatus;
DraggableList.tsx
import type {DivProps} from "./Div";
import type {FC} from "react";
import {useRef} from "react";
import {useDrag, useDrop} from "react-dnd";
import type {Identifier} from 'dnd-core';
export type MoveFunc = (dragIndex: number, hoverIndex: number) => void
export type ListDraggableProps = DivProps & {
id: any
index: number
onMove: MoveFunc
}
interface DragItem {
index: number
id: string
type: string
}
// ListDraggable 컴포넌트는 드래그 앤 드롭 기능을 제공하는 리스트 아이템을 렌더링한다
export const ListDraggable: FC<ListDraggableProps> = ({ id, index, onMove, style, className, ...props }) => {
// ref는 드래그 및 드롭을 위해 div 요소를 참조한다
const ref = useRef<HTMLDivElement>(null);
// useDrop 훅을 사용하여 드롭 대상 설정
const [{ handlerId }, drop] = useDrop<DragItem, void, { handlerId: Identifier | null }>({
accept: 'list', // 'list' 타입의 아이템을 받아들임
collect(monitor) {
return {
handlerId: monitor.getHandlerId() // 드롭 대상의 핸들러 ID 수집
}
},
hover(item: DragItem) {
if (!ref.current) {
return;
}
const dragIndex = item.index;
const hoverIndex = index;
// 드래그 중인 아이템의 인덱스와 호버 중인 아이템의 인덱스가 같으면 아무 작업도 하지 않음
if (dragIndex === hoverIndex) {
return;
}
// 아이템 이동 함수 호출
onMove(dragIndex, hoverIndex);
// 드래그 중인 아이템의 인덱스를 호버 중인 인덱스로 업데이트
item.index = hoverIndex;
}
});
// useDrag 훅을 사용하여 드래그 소스 설정
const [{ isDragging }, drag] = useDrag({
type: 'list', // 'list' 타입의 아이템
item: () => {
return { id, index };
},
collect: monitor => ({
isDragging: monitor.isDragging() // 드래그 중인지 여부 수집
})
});
// 드래그 중일 때 투명도 설정
const opacity = isDragging ? 0 : 1;
// ref를 드래그 및 드롭 훅에 연결
drag(drop(ref));
return (
<div
ref={ref}
{...props}
className={[className, 'cursor-move'].join(' ')} // 커서 모양을 이동 커서로 설정
style={{ ...style, opacity }} // 스타일과 투명도 설정
data-handler-id={handlerId} // 데이터 핸들러 ID 설정
/>
);
};
'Javascript > React' 카테고리의 다른 글
[React] 리액트 라우터 (0) | 2024.08.01 |
---|---|
[React] React Beautiful DnD 라이브러리 (0) | 2024.07.31 |
[React] 리덕스 미들웨어 (0) | 2024.07.29 |
리듀서 활용하기 (0) | 2024.07.22 |
리덕스 기본 개념 이해하기 (2) | 2024.07.21 |