프레임워크\라이브러리/React

[라이브러리] @hello-pangea/dnd로 드래그 앤 드롭 구현

thinktank911 2025. 11. 20. 15:55

드래그 앤 드롭 기능을 구현하기 위해 지원이 중단된 react-beautiful-dnd의 대안으로 그 포크 라이브러리인 @hello-pangea/dnd를 사용해보았다.

 

https://github.com/hello-pangea/dnd?tab=readme-ov-file

 

dnd 사용 요소

DragDropContext

드래그 앤 드롭을 활성화하려는 어플리케이션 부분을 감싼다.

onDragEnd 속성을 통해 드래그 종료 시 호출될 함수를 설정할 수 있다.

<DragDropContext onDragEnd={handleEnd}>

 

Droppable

드래그한 항목이 놓일 수 있는 영역이다. droppableId 속성으로 해당 영역을 식별한다.

<Droppable droppableId='characters'>

 

Draggable

드래그할 수 있는 요소이다.각 항목은 draggableId로 고유하게 식별되며, index는 배열 내 위치를 나타낸다.

<Draggable key={id} draggableId={id} index={index}>

 

상태 바꿔주기

onDragEnd

드래그 작업 종료 시 호출되는 함수로, result라는 매개변수를 통해 드래그 작업에 대한 정보를 받는다. 기본적인 개념은 배열을 재정렬하는 것이며 항목들이 재정렬된 순서대로 업데이트되도록 만들어야 한다.

 

result

  • source : 원래 있던 자리 위치 정보
  • destination : 드래그로 옮긴 자리 위치 정보

 

 

드래그 유효성 검사

드래그가 유효하지 않은 경우(예: 리스트 밖으로 드롭했을 때), 함수는 바로 종료된다. result.destination이 없다는 것은 항목이 유효하지 않은 위치에 드롭되었음을 의미한다.

// 목적지 없으면 함수 종료
if (!result.destination) return;

 

배열 얕은 복사

리액트 불변성 지켜주기 위해 Array.from 사용해 새 배열 만들어준다.

// 리액트 불변성을 지켜주기 위해 새로운 Data 생성
const items = Array.from(characters);

 

배열 재정렬

1. 변경시키는 아이템을 배열에서 지워주기
2. return 값으로 지워진 아이템 잡아준다.

const [reorderedItem] = items.splice(result.source.index, 1);


3. 원하는 자리에 reorderedItem을 insert 해준다.

items.splice(result.destination.index, 0, reorderedItem);

 

※ splice 사용법

array.splice(start[, deleteCount[, item1[, item2[, ...]]]])

매개변수

  • start : 배열의 변경을 시작할 인덱스
  • deleteCount Optional :배열에서 제거할 요소의 수. deleteCount를 생략하거나 값이 array.length - start보다 크면 start부터의 모든 요소를 제거. deleteCount가 0 이하라면 어떤 요소도 제거하지 않는다. 이 때는 최소한 하나의 새로운 요소를 지정해야 한다.
  • item1, item2, <em>...</em> Optional : 배열에 추가할 요소. 아무 요소도 지정하지 않으면 splice()는 요소를 제거하기만 한다.

반환 값

제거한 요소를 담은 배열. 하나의 요소만 제거한 경우 길이가 1인 배열을 반환. 아무 값도 제거하지 않았으면 빈 배열을 반환

 

 

상태 업데이트

  • useState 상태 업데이트 해준다.
const [characters, setcharacters] = useState(finalSpaceCharacters);

setcharacters(items);

 

provided 셋팅

<Droppable />

  • droppable로 감싸줄 태그를 (provided) => (<태그>) 로 한번 더 감싼다.
  • {provided.placeholder} : 해당 태그의 닫는 태그 바로 위 위치. 드래그 시 공간을 주어 자연스럽게 보이도록 하기 위함
  • ref와 {...provided.droppableProps} 넣어준다.

 

<Draggable />

  • draggable로 감싸줄 태그를 (provided) => (<태그>) 로 한번 더 감싼다.
  • ref와 {...provided.draggableProps}, {...provided.dragHandleProps} 넣어준다.
<Droppable droppableId='characters'>
  {(provided) => (
    <ul className='characters' 
      {...provided.droppableProps} 
      ref={provided.innerRef}
    >
      {
        characters.map(({id, name}, index) => {
          return (
            <Draggable key={id} draggableId={id} index={index}>
              {(provided) => (
                <li 
                  ref={provided.innerRef}
                  {...provided.draggableProps}
                  {...provided.dragHandleProps}
                >
                  <p>
                    {name}
                  </p>
                </li>
              )}
            </Draggable>
          )
        })
      }
      {provided.placeholder}
    </ul>
  )}

</Droppable>