🔍 직접 구현하지 않고 라이브러리를 쓴 이유가 무엇인가요?
직접 구현해 보는 게 실력 향상에 도움도 될 것이고 재미도 있을 것 같지만, 굉장한 시간이 소모될 것이라고 생각했습니다.
현재는 솔로 프로젝트를 진행하는 것이 아니고 정해진 시간 안에 팀원들과 협업하여 프로젝트를 완성해야 하는 상황이기 때문에, 라이브러리를 사용하여 어떤 느낌인지 맛 좀 보고, 빠르게 완성했습니다.
🔍 react-dnd가 아니라 react-beautiful-dnd를 선택한 이유가 무엇인가요?
react-dnd는 react-beautiful-dnd에 비해 자유도도 높고 커스텀하기도 훨씬 편하다고 합니다. 하지만 변경 애니메이션이나 위치 변경을 직접 정의해야 한다는 단점이 있었습니다.
저는 굳이 커스텀을 해야할 정도로 영역이 크지도 않고, 위-아래로만 움직이면서 애니메이션까지 편하게 사용할 수 있는 react-beautiful-dnd를 선택했습니다.
🎯 구현 슈웃
😡 React.StrictMode 사용 시 에러 해결
구현 전에 먼저 말씀드립니다. React.StrictMode를 사용하고 있다면 드래그 이벤트를 발생시킬 때, 위의 에러가 드래그가 끝날 때까지 발생합니다. 해결 방법은 두 가지가 있습니다.
첫 번째로는 그냥 React.StrictMode를 제거하시면 됩니다.
두 번째로는 아래의 이슈 링크를 참고하셔서 작성하시면 됩니다.
https://github.com/atlassian/react-beautiful-dnd/issues/2399#issuecomment-1175638194
import { useEffect, useState } from 'react';
import { Droppable, DroppableProps } from 'react-beautiful-dnd';
export 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>;
};
이 컴포넌트를 따로 만들어주신 다음, Droppable 대신 사용해 주시면 에러가 사라집니다!
😇 DragDropContext로 감싸기
return (
<DragDropContext onDragEnd={onDragEnd}>
...
</DragDropContext>
);
드래그 앤 드롭을 사용하기 위해서는 DragDropContext로 모두 감싸줘야만 합니다.
그러면 Droppable과 Draggable을 사용하여 조건부 드래그 앤 드롭을 사용할 수 있게 됩니다!
전달해야 하는 props로 onDragStart와 onDragEnd가 있는데, onDragEnd는 필수로 전달해줘야 합니다.
😆 onDragEnd 함수
const onDragEnd = ({ source, destination }: DropResult) => {
if (!destination) return;
const copySchedules = JSON.parse(JSON.stringify(schedules)) as TScheduleList;
const tempSchedules = copySchedules.splice(source.index, 1);
copySchedules.splice(destination.index, 0, ...tempSchedules);
dispatch(scheduleListActions.updateList(copySchedules));
};
onDragEnd 함수는 요소를 드래그한 뒤에 드롭했을 때 실행되는 함수입니다.
드래그한 요소를 이상한 곳에 드롭하면 destination이 null이므로 바로 return을 시켜서 함수를 종료시킵니다.
드롭이 정상적으로 되었다면, 드래그할 배열들을 깊은 복사를 통해서 복사해 놓습니다. 그다음 splice 메서드를 이용해서 배열의 순서를 업데이트합니다. 마지막으로 전역에서 관리되고 있는 배열을 업데이트합니다.
😅 Droppable
return (
<DragDropContext onDragEnd={onDragEnd}>
<Wrapper>
<StrictModeDroppable droppableId="schedules">
{(provided) => (
<div ref={provided.innerRef} {...provided.droppableProps}>
...
{provided.placeholder}
</div>
)}
</StrictModeDroppable>
</Wrapper>
</DragDropContext>
);
고유한 droppableId로 드롭이 가능한 지 식별합니다.
provided.innerRef는 요소에 반드시 바인드해야 droppable이 작동한다고 합니다.
provided.droppableProps는 provided.innerRef를 적용한 것과 동일한 요소에 적용해야 합니다. 현재 스타일링 및 조회에 사용하는 데이터 속성이 포함되어 있습니다.
provided.placeholder는 드롭될 공간을 만들기 위해서 필요하다고 합니다. 없어도 동작은 되지만 에러를 띄웁니다...
😄 Draggable
<div ref={provided.innerRef} {...provided.droppableProps}>
{schedules.map((schedule: IScheduleListItem, idx: number) => (
<Draggable key={schedule.id} draggableId={schedule.id} index={idx}>
{(provided_two) => (
<div ref={provided_two.innerRef} {...provided_two.draggableProps} {...provided_two.dragHandleProps}>
<MapLocationCard indexNum={idx + 1} location={schedule.placeName} id={schedule.id}/>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
Drappable과 비슷합니다!
🥹 전체 코드
import { useDispatch, useSelector } from 'react-redux';
import { DragDropContext, Draggable, DropResult } from 'react-beautiful-dnd';
import { RootState } from '../../store';
import { IScheduleListItem, TScheduleList } from '../../types/type';
import { scheduleListActions } from '../../store/scheduleList-slice';
import { StrictModeDroppable } from '../dnd/StrictModeDroppable';
const ScheduleListBox = () => {
const dispatch = useDispatch();
const schedules = useSelector((state: RootState) => state.scheduleList.list);
const onDragEnd = ({ source, destination }: DropResult) => {
if (!destination) return;
const copySchedules = JSON.parse(
JSON.stringify(schedules)
) as TScheduleList;
const targetSchedules = copySchedules.splice(source.index, 1);
copySchedules.splice(destination.index, 0, ...targetSchedules);
dispatch(scheduleListActions.updateList(copySchedules));
};
return (
<DragDropContext onDragEnd={onDragEnd}>
<Wrapper>
<StrictModeDroppable droppableId="schedules">
{(provided) => (
<div ref={provided.innerRef} {...provided.droppableProps}>
{schedules.map((schedule: IScheduleListItem, idx: number) => (
<Draggable
key={schedule.id}
draggableId={schedule.id}
index={idx}
>
{(provided_two) => (
<div
ref={provided_two.innerRef}
{...provided_two.draggableProps}
{...provided_two.dragHandleProps}
>
<MapLocationCard
indexNum={idx + 1}
location={schedule.placeName}
id={schedule.id}
/>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</StrictModeDroppable>
</Wrapper>
</DragDropContext>
);
};
export default ScheduleListBox;
스타일에 관련한 코드는 제외했습니다!
📌 레퍼런스
영어
https://github.com/atlassian/react-beautiful-dnd
한국어 (내용이 살짝 없어 보입니다)
https://github.com/LeeHyungGeun/react-beautiful-dnd-kr
이 분이 안 계셨다면 라이브러리임에도 불구하고 시간이 걸렸을 것 같습니다..!!
압도적...감사...!!!
https://bepyan.github.io/blog/dnd-master/6-react-beautiful-dnd
'Project' 카테고리의 다른 글
[하루메이트] 3일동안 한거라곤 반응형과 버그 잡기 - 22.07.13~15 (0) | 2023.07.15 |
---|---|
[하루메이트] 오늘의 회고 - 22.07.12 (0) | 2023.07.12 |
[하루메이트] 오늘의 회고 - 22.07.10 (0) | 2023.07.10 |
[하루메이트] 오늘의 회고 - 22.07.05 (0) | 2023.07.05 |
[영화 검색 & 추천 사이트] 회고 (3) | 2023.06.05 |