웹 디자인 도구나 레이아웃 빌더를 개발하면서 사용자가 요소들을 자유롭게 이동하고 그룹화할 수 있는 인터페이스의 필요성을 느꼈습니다. 이를 위해 react-beautiful-dnd 라이브러리를 선택하여 직관적이고 부드러운 드래그 앤 드롭 경험을 구현했습니다.
react-dnd가 아니라 react-beautiful-dnd를 선택한 이유가 무엇인가요?
react-dnd는 react-beautiful-dnd보다 자유도가 높고, 커스텀하기에도 훨씬 유리하다는 장점이 있습니다. 하지만 위치 변경 애니메이션이나 드래그 후 이동 로직을 직접 정의해야 한다는 단점이 있었습니다.
저는 이번 과제에서 복잡한 커스텀 작업이 필요하지 않았고, 단순히 위아래로 요소를 이동하면서 기본적인 애니메이션이 자연스럽게 적용되는 라이브러리가 더 적합하다고 판단했습니다. 이러한 이유로 직관적이고 간편하게 사용할 수 있는 react-beautiful-dnd를 선택하게 되었습니다.
구조
DragDropContext
- DragDropContext은 Drag and Drop이 일어나는 전체영역이다.
- 필수 함수:
- onDragStart: 드래그가 시작될 때 실행되는 함수. (Optional)
- onDragEnd: 드래그가 끝날 때 실행되는 함수. (Required)
<DragDropContext>
<Droppable droppableId="dndlists">
{provided => (
<anyOtherComponent className="dndlists" {...provided.droppableProps} ref={provided.innerRef}>
// 여기에 움직일 컴포넌트를 넣어줄 예정
</anyOtherComponent>
)}
</Droppable>
</DragDropContext>
Droppable
- 드롭 가능한 영역을 정의하는 컴포넌트입니다.
- 필수 속성:
- droppableId: 각 드롭 영역의 고유 ID를 정의합니다.
<DragDropContext onDragEnd={handleChange}>
<Droppable droppableId="dndlists">
{provided => (
<div className="dndlists" {...provided.droppableProps} ref={provided.innerRef}>
✅
{post.map((e: any, i: number) => (
{/* Draggable 컴포넌트가 들어갑니다 */}
))}
</div>
)}
</Droppable>
</DragDropContext>
Droggable
- 드래그 가능한 항목을 정의하는 컴포넌트입니다.
- 필수 속성:
- draggableId: 각 드래그 항목의 고유 ID를 정의합니다.
- index: 드래그 항목의 위치를 나타냅니다.
<DragDropContext onDragEnd={handleChange}>
<Droppable droppableId="dndlists">
{provided => (
<div className="dndlists" {...provided.droppableProps} ref={provided.innerRef}>
{post.map((e: any, i: number) => (
<Draggable draggableId={`test-${e.id}`} index={i} key={`test-${e.id}`}>
{(provided, snapshot) => {
return (
<div
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
>
// 원하는 컴포넌트 넣어주기
</div>
);
}}
</Draggable>
))}
</div>
)}
</Droppable>
</DragDropContext>
🔥 Draggable 의 draggableId와 key가 같아야 한다.
<Draggable draggableId<={`test-${e.id}`} index={i} key={`test-${e.id}`}>
- Droppable과 마찬가지로 html elemnent를 이용해 props를 넘겨주어야한다. (넘겨주지 않으면 동작하지 x)
함수
onDragStart
- 드래그가 시작될 때 호출됩니다.
- 반환 값:
- source.droppableId: 시작 영역의 ID.
- source.index: 시작 영역의 인덱스.
onDragEnd
- 드래그가 끝날 때 호출됩니다. 필수로 정의해야 합니다.
- 반환 값:
- source: 드래그 시작 위치 정보.
- destination: 드래그 종료 위치 정보.
- reason: 드래그 종료 이유.
실제 구현 사례
프로젝트 구조
프로젝트는 크게 세 가지 주요 컴포넌트로 구성했습니다.
- ElementComponent: 개별 요소 렌더링
- LayerComponent: 레이어 패널 및 요소 관리
- ViewportComponent: 작업 영역 및 요소 배치
핵심 코드 분석
1. Droppable 영역 설정
<Droppable droppableId="viewport" direction="horizontal">
{(provided) => (
<ViewportContainer
{...provided.droppableProps}
ref={provided.innerRef}
>
{/* 요소들 렌더링 */}
{provided.placeholder}
</ViewportContainer>
)}
</Droppable>
2. Draggable 요소 구현
<Draggable
key={element.id}
draggableId={element.id}
index={index}
>
{(provided) => (
<ViewportItem
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<ElementComponent
type={element.type}
isSelected={selectedElementIndex.includes(actualIndex)}
/>
</ViewportItem>
)}
</Draggable>
주요 기능
- 요소 생성: 사용자가 div, span, p 태그를 동적으로 추가 가능
- 요소 선택: Shift 키를 이용한 다중 선택 지원
- 그룹화: 요소들을 그룹으로 묶어 함께 관리
상태 관리 전략
- elements: 전체 요소 배열
- groups: 요소 그룹 정보
- selectedElementIndex: 현재 선택된 요소 인덱스
드래그 종료 핸들러
const onDragEnd = (result) => {
const { source, destination } = result;
// 유효하지 않은 드래그 처리
if (!destination) return;
// 요소 재정렬 로직
const newElements = Array.from(elements);
const [reorderedItem] = newElements.splice(source.index, 1);
newElements.splice(destination.index, 0, reorderedItem);
// 상태 업데이트
setElements(newElements);
};
학습 및 개선 포인트
- 초기에는 복잡한 드래그 앤 드롭 로직에 어려움을 겪었습니다.
- 그룹화와 다중 선택 기능 구현에 많은 시간을 투자했습니다.
- 성능 최적화를 위해 React.memo()와 같은 최적화 기법 적용
느낀 점
react-beautiful-dnd는 간단한 드래그 앤 드롭 기능부터 복잡한 인터랙티브 인터페이스까지 다양하게 활용할 수 있는 강력한 라이브러리입니다. 하지만 최신 React(18) 기능과의 완전한 호환성 보장이 어렵고, 현재 적극적인 유지보수가 되고 있지 않아서 장기적인 프로젝트에서는 다른 대안을 고려해야 합니다.
'React.js' 카테고리의 다른 글
[React] React에서 URL 쿼리 vs API 쿼리 호출: 언제 어떤 걸 써야 할까? (0) | 2025.04.14 |
---|---|
[React]React useImperativeHandle Hook 깊게 이해하기 (0) | 2025.02.01 |
[React]React useActionState Hook에 대해 알아보기 (0) | 2024.12.31 |
[React] 서버와 통신하며 임시저장 기능 구현하기 (2) | 2024.10.10 |
[React] React에서의 실시간 검색 기능 최적화: Debounce 기법 적용 경험 (4) | 2024.10.09 |