[React] react-beautiful-dnd 사용하기

thousand_yj·2023년 7월 15일
0

Willing 프로젝트

목록 보기
8/18

✨ Drag-and-Drop

현재 개발중인 프로젝트에서 드래그 앤 드롭으로 아이템의 순서를 바꾸거나 삭제하기 위해 기능을 직접 js(ts)로 구현하려다가 시간이 오래 걸릴 듯 하여 외부 라이브러리를 사용하기로 했다.
React-beautiful-dnd라는 이름의 라이브러리를 설치해주었다.

구조

  • <DragDropContext /> : 드래그 앤 드롭을 가능하게 허용할 부분을 감싸주는 태그
    • onDragEnd : 드래그를 끝낸 시점에 호출되는 함수 (필수 속성)

  • <Droppable /> : 드롭이 가능한 부분을 감싸주는 태그
    • droppableId : 구분자 (필수 속성)
    • children은 반드시 함수여야 함 (JSX 태그X) -> 따라서 함수로 JSX 태그를 리턴하는 형식을 자식에 넣어주어야 한다
      • Droppable이 내부 함수에 전달해주는 인자 : provided
        • 내부 JSX 태그에 ref={provided.innerRef} {...provided.droppableProps} 속성을 부여해줘야 한다

  • <Draggable /> : 드래그가 가능한 부분을 감싸주는 태그
    • draggableId : 구분자 (필수 속성)
    • index : 정렬을 위한 데이터 (number)
    • children은 반드시 함수여야 함 (JSX 태그X) -> 따라서 함수로 JSX 태그를 리턴하는 형식을 자식에 넣어주어야 한다
      • Draggable이 내부 함수에 전달해주는 인자 : provided
        • provided.draggableProps : 태그의 모든 영역에서 드래그 가능하도록 설정
        • provided.dragHandleProps : 특정 영역에서만 드래그 가능하도록 설정

드래그앤드롭이 발생할 때 리스트의 길이가 바뀐다면

<Droppable /> 에서 가장 끝부분에 {provided.placeholder} 를 부여하여 가장 끝이 어딘지 알 수 있도록 설정한다.

import Item from "../components/Item";
import { InterfaceContent } from "../atoms";
import { Droppable } from "react-beautiful-dnd";

type ListProps = {
  title: string;
  contents: InterfaceContent[];
  listId: string;
};
function List({ title, contents, listId }: ListProps) {
  return (
    <Droppable droppableId={listId}>
      {(provided) => (
        <div className="pt-2">
          <p className="text-lg font-bold"> {title}</p>
          <ul ref={provided.innerRef} {...provided.droppableProps}>
            {contents.map((content, index) => (
              <Item key={content.id} index={index} {...content} />
            ))}
          </ul>
          {provided.placeholder}
        </div>
      )}
    </Droppable>
  );
}

export default List;

드래그앤드롭이 끝났을 때 순서를 실제로 바꿔주려면

위의 설정까지만 부여하고 나면 드래그앤드롭 애니메이션이 가능하나 마우스를 떼는 순간 다시 원래대로 돌아가버린다. 따라서 <DragDropContext />onDragEnd에 순서를 변경해주는 함수를 넣어줘야 한다.

드래그 중에만 특정 아이콘을 나타나게 하고 싶다면

  • DragDropContext (자세한 설명은 여기를 참고)
    • onBeforeCapture : a drag is about to start and dimensions have not been collected from the DOM. 예제를 보니 드래그가 시작할때 쓰레기통이 생기는 요소를 이 함수로 사용한다.
    • onDragStart : 드래그가 시작할 때 호출
    • onDragUpdate: 드래그가 시작할 때 & 요소 간의 순서가 바뀔 때 호출

따라서 onBeforeCapture 함수에게 쓰레기통이 보이도록 하는 소스코드를 추가해주면 된다.

드롭 애니메이션 없애는 방법

개발팀의 github 링크 보고 구현했다.

const getStyle = (
    style: DraggingStyle | NotDraggingStyle,
    snapshot: DraggableStateSnapshot,
    isOverTrashCan: boolean
  ) => {
    if (!snapshot.isDropAnimating || !isOverTrashCan) {
      return style;
    }
    return {
      ...style,
      // cannot be 0, but make it super tiny
      transitionDuration: `0.001s`,
    };
  };
 <Draggable draggableId={item.id + ""} index={index}>
      {(provided, snapshot) => (
        <ItemCard
          ref={provided.innerRef}
          {...provided.draggableProps}
          {...provided.dragHandleProps}
          isOverTrashCan={isOverTrashCan}
          isDragging={snapshot.isDragging}
          style={getStyle(
            provided.draggableProps.style!,
            snapshot,
            isOverTrashCan
          )}
        >
        //...

쓰레기통 위에 올라갔을 때는 draggable요소 색 바꾸기

드래그가 발생할 때마다 쓰레기통 위에 올라갔는지 체크해주면 된다. DragDropContext
onDragUpdate 메소드에서 체크해준다.

 <DragDropContext
      onDragEnd={dragEndHandler}
      onBeforeCapture={() => setIsDragging(true)}
      onDragUpdate={(update) => {
        if (update.destination?.droppableId === "trashcan") {
          setIsOverTrashCan(true);
        } else {
          setIsOverTrashCan(false);
        }
      }}
    >
      // ...
</DragDropContext>

그리고 드래그가 발생한 아이템만 색을 변경해줘야 하므로 Draggable 요소에서 두번째 인자로 전달되는 snapshotisDragging 값을 가져온다.

<Draggable draggableId={item.id + ""} index={index}>
      {(provided, snapshot) => (
        <ItemCard
          ref={provided.innerRef}
          {...provided.draggableProps}
          {...provided.dragHandleProps}
          isOverTrashCan={isOverTrashCan}
          isDragging={snapshot.isDragging}
        >
        // ...
</Draggable>

interface ICard {
  isOverTrashCan: boolean;
  isDragging: boolean;
}
const ItemCard = styled.li<ICard>`
  // ...
  box-shadow: ${(props) =>
    props.isOverTrashCan && props.isDragging ? "0 0 0.3rem #d34747b4" : ""};
`;

실제 CSS가 변경될 때는 쓰레기통 위에 올라가있는지 & 현재 움직이고 있는 요소인지를 동시에 체크해서 값을 넘겨줘야 한다.

profile
함께 일하고 싶은 개발자가 되기 위해 노력합니다. 코딩테스트 관련 공부 및 이야기는 티스토리에도 업로드되어 있습니다.

0개의 댓글