패스트캠퍼스 데브캠프

토이프로젝트 3 - react-beautiful-dnd 라이브러리 사용법

vitamin3000 2025. 1. 8. 21:59

 

플레이리스트 상세 페이지에서 동영상의 인덱스 위치값을 수정하는 기능을 구현하고자 하였다.

 

처음에는 HTMLEVENT를 활용한 드래그 앤 드랍 hook을 제작하였는데,

이 과정에서 데스크탑에서는 동작하지만 모바일 환경에서는 터치라는 새로운 개념으로 둘 다 만들어야 해서

 

hook을 제거하고 관련 라이브러리 중에서 react-beautiful-dnd라는 스와핑 라이브러리가 있기에 사용해보고자 한다.

 

아래의 사용방법과 코드 예시는 다음의 페이지를 참고하였다.(제가 작성한 글이 문제가 된다면 언제든지 삭제하겠습니다)

https://intothe-universe.tistory.com/54

 

 

아래와 같은 동영상 플레이리스트가 존재한다고 가정해보자.

 

1 -> 2-> 3의 동영상 순서를 1 -> 3 -> 2로 바꾸고 싶다.

 

여기서 다양한 방법이 있겠지만, 가장 생각하기 쉬운 드래그 앤 드랍 동작을 통해 위의 결과물을 만들고자한다.

 

우선, 드래그와 드라베 대해 알아보자.

2나 3의 요소를 집어 올리고, 2의 위치 또는 3의 위치에 내려놓아야 한다.

a. 집어서 옮기고 => draggable

b. 내려놓는 => droppable

위의 두가지 동작으로 이해하면 되겠다.

 

이러한 동작으로 알 수 있는 점은 droppable한 곳에서 다시 집어 옮겨야하니 draggable 해야한다.

 

아래의 이미지는 이를 시각적으로 표현하였다.

출처 :: https://blog.kakaocdn.net/dna/Wr57d/btsIy0peZ0r/AAAAAAAAAAAAAAAAAAAAABy9hY8uhkodkxtnGcPudwhpJNgxE9jfkKjVf7r3ulOJ/img.gif?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&expires=1753973999&allow_ip=&allow_referer=&signature=uGY2dXzxUCSiRJ7SG2NFrGkb004%3D

 

react-beautiful-dnd 사용에서도 동일하게 동작하는데,

 

  • DragDropContext
    • 사용할 전체 영역을 감싼다
  • Droppable
    • 요소를 내려 놓을 수 있는 공간으로, 내려놓는 곳에서 다시 집어서 옮기려면 그 위치의 Draggable 요소와 스와핑한다.
  • Draggable
    • 집어서 옮길 수 있는 요소들

다음은 라이브러리에서 사용하는 함수에 대한 설명이다.

  • DragDropContext
    • onDragEnd: DragDropContext의 필수 함수로, 드래그가 종료되었을 떄 발생할 일을 담아준다
    • onDragStart: 필수 함수는 아니지만 드래그가 시작될 때 발생할 일을 담아준다.
  • Droppable
    • droppabledId: string으로 지정, 드롭 가능한 곳을 구분해주기위한 Id
    • provided
      • provided.innerRef : ref에 걸어주어야 한다  드래그 앤 드랍은 돔을 조작하는 일이기에
      • provided.placeholder : drop될 때 영역을 미리 잡아놓는 역할을 수행
      • snapshot : 필수 요소는 아니지만, 드래그 앤 드롭할 때 스타일을 설정할 수 있다.
  • Draggable
    • draggableId : 드래그할 영역을 구분할 Id
    • index : 반드시 순서대로 입력되어야 한다.
    • provided
      • provided.innerRef : ref에 걸어주어야 한다  드래그 앤 드랍은 돔을 조작하는 일이기에
      • provided.draggableProps : 돔에 등록하기 위해 필요
      • provided dragHandleProps draggableProps와 동일
      • snapshot : 필수 요소는 아니지만, 드래그 앤 드롭할 때 스타일을 설정할 수 있다.

 

아래의 onDragEnd 함수를 살펴보자

  const onDragEnd = (result: DropResult) => {
    if (!result?.destination) return;

    const sourceIndex = result.source.index;
    const destinationIndex = result.destination.index;
    const newList = [...videoList];
    const pickedItem = newList[sourceIndex];

    newList.splice(sourceIndex, 1);
    newList.splice(destinationIndex, 0, pickedItem);
    setVideoList(newList);
  };

 

DropResult 타입으로 result를 props로 받는다.

이 result는 destination과 source를 받는데,

 

result.destination이 없다는 건 목적지가 없다 -> 위치할 수 없는 곳으로 드래그를 시도했다는 뜻으로, 그냥 반환

 

result에는 draggableId와 destination에 해당하는 droppableId와 index를 갖고 있으므로 드래그된 이후의 액션을 설정한다.

const sourceIndex = result.source.index; // 드래그해 온 요소
const destinationIndex = result.destination.index; // 드래그 한 목적지(위치)

 

우리의 목적은 해당하는 두 요소의 위치를 바꿔주는 것이므로 

 

    const newList = [...videoList];
    const pickedItem = newList[sourceIndex];

    newList.splice(sourceIndex, 1);
    newList.splice(destinationIndex, 0, pickedItem);

 

적용한 알고리즘 

기존의 list를 복사하고, 드래그해온 요소를 pickedItem에 저장한다.

복사한 리스트에서 sourceIndex(드래그 해온 요소)를 삭제하고, 저장한 pickedItem에 붙여넣으며 인덱스를 변경한다.

 

결과적으로 두 요소의 위치가 바뀌는 것이다.

 

이것이 이해가 잘 되지 않는 다면, 값을 교환하는 swap의 개념을 적용해보면 쉽다.

 

a = 1

b = 2

두 변수가 존재한다고 가정해보자, 

a의 값을 b에 저장한다고 바로 b = a를 실행핟다면 b의 값을 사라지게되기에, tmp라는 임시변수를 만들어 a의 값을 b에 저장하기 전에 먼저 tmp에 b의 값을 저장한다.

이를 코드로 표현하자면

tmp = b

b = a

a = tmp

가 되겠다

 

이를 위의 적용한 알고리즘에 대입해보면 이해가 쉬울 것이다.

 

 

결과화면

WeFit - Chrome 2025-01-08 12-00-06.mp4
2.36MB

데스크탑 환경

WeFit - Chrome 2025-01-08 11-59-19.mp4
3.13MB

모바일 환경 

 

추가적으로

참고한 블로그에서는 beautiful-dnd 라이브러리의 드래그앤드랍 동작이 수행되지 않을경우 Strict Mode를 삭제하라고 했는데,

이는 개발 환경에서 strict의 이점을 포기하기되므로 다른 방법을 찾아보았다.

 

기존의 Droppable를 아래의 함수로 대체한다.

const StrictModeDroppable = ({ children, ...props }: DroppableProps) => {
  const [enabled, setEnabled] = useState(false);

  useEffect(() => {
    const animation = requestAnimationFrame(() => setEnabled(true));

    return () => {
      cancelAnimationFrame(animation);
      setEnabled(false);
    };
  }, []);

  if (!enabled) {
    return null;
  }

  return <Droppable {...props}>{children}</Droppable>;
};

 

+) 추가적으로

 

현재 

 

플레이리스트 상세페이지에서 동영상 스와핑 기능 구현을 위해 react-beatufiul-dnd 라이브러리를 사용하였다.
이 과정에서, react 18버젼에서 defaultProps가 삭제될 것이라는 warning 알림이 발생한다

해결 방법

 

npm install @hello-pangea/dnd

위 코드를 실행하여 라이브러리를 설치하고,

react-beautiful-dnd를 @hello-pangea/dnd로 수정하면 warning이 발생하지 않는다!