플레이리스트 상세 페이지에서 동영상의 인덱스 위치값을 수정하는 기능을 구현하고자 하였다.
처음에는 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 해야한다.
아래의 이미지는 이를 시각적으로 표현하였다.
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
가 되겠다
이를 위의 적용한 알고리즘에 대입해보면 이해가 쉬울 것이다.
결과화면
데스크탑 환경
모바일 환경
추가적으로
참고한 블로그에서는 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이 발생하지 않는다!
'패스트캠퍼스 데브캠프' 카테고리의 다른 글
토이 프로젝트 3 - zod와 react-hook-form 사용하여 유효성 검사 (0) | 2025.01.12 |
---|---|
김민태의 데브캠프 2기 - 실시간 강의 (01/09) (0) | 2025.01.09 |
React 스플래시 페이지 애니메이션 적용하기 (0) | 2025.01.02 |
React useDebounce에 대해 알아보자 (0) | 2025.01.02 |
김민태의 데브캠프 2기 - Typescript vs Javascript (0) | 2024.12.24 |