用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/puk0806/gugbab-claude --skill react-dnd命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | react-dnd |
| description | react-dnd 드래그 앤 드롭 — DndProvider, useDrag/useDrop 훅, TypeScript 타입, 리스트 순서 변경, 커스텀 프리뷰, 중첩 드롭, SSR 주의사항 |
소스: https://react-dnd.github.io/react-dnd/docs/overview 소스: https://github.com/react-dnd/react-dnd 검증일: 2026-04-20
npm install react-dnd react-dnd-html5-backend
# 터치 지원이 필요하면:
npm install react-dnd-touch-backend
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
function App() {
return (
<DndProvider backend={HTML5Backend}>
<MyDragDropContent />
</DndProvider>
);
}
// 아이템 타입 상수 정의
const ItemTypes = {
CARD: 'card',
COLUMN: 'column',
} as const;
// 드래그 아이템 인터페이스
interface DragItem {
type: string;
id: string;
index: number;
}
import { useDrag } from 'react-dnd';
function DraggableCard({ id, text, index }: CardProps) {
const [{ isDragging }, dragRef] = useDrag(() => ({
type: ItemTypes.CARD,
item: { id, index },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
end: (item, monitor) => {
const dropResult = monitor.getDropResult();
if (item && dropResult) {
// 드롭 완료 후 처리
}
},
}), [id, index]);
return (
<div ref={dragRef} style={{ opacity: isDragging ? 0.5 : 1 }}>
{text}
</div>
);
}
| 옵션 | 타입 | 설명 |
|---|---|---|
type | string | 필수. 드래그 아이템 유형 식별자 |
item | object | () => object | 드래그 데이터. 함수면 드래그 시작 시 호출 |
collect | (monitor) => object | monitor 상태를 컴포넌트 props로 매핑 |
end | (item, monitor) => void | 드래그 종료 콜백 |
canDrag | (monitor) => boolean | 드래그 가능 여부 제어 |
isDragging | (monitor) => boolean | 커스텀 isDragging 판별 로직 |
const [collectedProps, dragRef, previewRef] = useDrag(spec, deps);
// collectedProps: collect 함수 반환값
// dragRef: 드래그 소스 엘리먼트에 연결
// previewRef: 드래그 미리보기 엘리먼트에 연결 (선택)
import { useDrop } from 'react-dnd';
function DropZone({ onDrop }: DropZoneProps) {
const [{ isOver, canDrop }, dropRef] = useDrop(() => ({
accept: ItemTypes.CARD,
drop: (item: DragItem) => {
onDrop(item.id);
return { name: 'DropZone' }; // end()의 getDropResult()로 전달
},
canDrop: (item: DragItem) => {
return item.id !== 'locked';
},
hover: (item: DragItem, monitor) => {
// 드래그 아이템이 위에 있을 때 반복 호출
},
collect: (monitor) => ({
isOver: monitor.isOver(),
canDrop: monitor.canDrop(),
}),
}), [onDrop]);
return (
<div
ref={dropRef}
style={{
backgroundColor: && ? '#' '#',
? ' ' ' ',
}}
>
Drop here
);
}
| 옵션 | 타입 | 설명 |
|---|---|---|
accept | string | string[] | 필수. 수락할 아이템 타입 |
drop | (item, monitor) => object | void | 드롭 시 호출. 반환값은 getDropResult() |
hover | (item, monitor) => void | 아이템이 위에 있을 때 반복 호출 |
canDrop | (item, monitor) => boolean | 드롭 수락 여부 |
collect | (monitor) => object | monitor 상태 수집 |
// 여러 타입 수락
accept: [ItemTypes.CARD, ItemTypes.COLUMN],
// 조건부 드롭 거부
canDrop: (item: DragItem, monitor) => {
// 특정 조건에서만 드롭 허용
return item.id !== currentId && !isLocked;
},
import { useRef } from 'react';
import { useDrag, useDrop } from 'react-dnd';
interface SortableItemProps {
id: string;
index: number;
text: string;
moveItem: (dragIndex: number, hoverIndex: number) => void;
}
function SortableItem({ id, index, text, moveItem }: SortableItemProps) {
const ref = useRef<HTMLDivElement>(null);
const [{ isDragging }, dragRef] = useDrag(() => ({
type: ItemTypes.CARD,
item: { id, index },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
}), [id, index]);
const [{ isOver }, dropRef] = useDrop(() => ({
accept: ItemTypes.CARD,
hover: (item: DragItem, monitor) => {
(!ref.) ;
dragIndex = item.;
hoverIndex = index;
(dragIndex === hoverIndex) ;
hoverBoundingRect = ref..();
hoverMiddleY = (hoverBoundingRect. - hoverBoundingRect.) / ;
clientOffset = monitor.();
(!clientOffset) ;
hoverClientY = clientOffset. - hoverBoundingRect.;
(dragIndex < hoverIndex && hoverClientY < hoverMiddleY) ;
(dragIndex > hoverIndex && hoverClientY > hoverMiddleY) ;
(dragIndex, hoverIndex);
item. = hoverIndex;
},
: ({
: monitor.(),
}),
}), [index, moveItem]);
((ref));
(
);
}
import { useCallback, useState } from 'react';
function SortableList() {
const [items, setItems] = useState([
{ id: '1', text: 'Item 1' },
{ id: '2', text: 'Item 2' },
{ id: '3', text: 'Item 3' },
]);
const moveItem = useCallback((dragIndex: number, hoverIndex: number) => {
setItems((prevItems) => {
const next = [...prevItems];
const [removed] = next.splice(dragIndex, 1);
next.splice(hoverIndex, 0, removed);
return next;
});
}, []);
return (
<DndProvider backend={HTML5Backend}>
{items.map((item, index) => (
<SortableItem
key={item.id}
id={item.id}
index={index}
text={item.text}
=
/>
))}
);
}
const [{ isDragging }, dragRef, previewRef] = useDrag(() => ({
type: ItemTypes.CARD,
item: { id },
collect: (monitor) => ({ isDragging: monitor.isDragging() }),
}));
return (
<>
{/* 이 엘리먼트가 드래그 프리뷰로 표시 */}
<div ref={previewRef}>
<CustomPreview />
</div>
{/* 이 엘리먼트를 잡아서 드래그 */}
<div ref={dragRef}>Drag Handle</div>
</>
);
import { useDragLayer } from 'react-dnd';
function CustomDragLayer() {
const { isDragging, item, currentOffset } = useDragLayer((monitor) => ({
item: monitor.getItem(),
isDragging: monitor.isDragging(),
currentOffset: monitor.getSourceClientOffset(),
}));
if (!isDragging || !currentOffset) return null;
return (
<div
style={{
position: 'fixed',
pointerEvents: 'none',
left: 0,
top: 0,
transform: `translate(${currentOffset.x}px, ${currentOffset.y}px)`,
zIndex: 9999,
}}
>
<div style={{ background: 'white', padding: 8, boxShadow: '0 2px 8px rgba(0,,,)' }}>
Dragging: {item?.id}
);
}
useDragLayer 사용 시 HTML5Backend 기본 프리뷰를 숨기려면:
import { getEmptyImage } from 'react-dnd-html5-backend';
import { useEffect } from 'react';
const [, dragRef, previewRef] = useDrag(() => ({
type: ItemTypes.CARD,
item: { id },
}));
useEffect(() => {
previewRef(getEmptyImage(), { captureDraggingState: true });
}, [previewRef]);
상세 레퍼런스 (예제·고급 패턴·흔한 실수) →
references/REFERENCE.md