React

[ReactTS] React Beautiful DnD (드래그 앤 드롭) 사용 방법

cob 2022. 9. 22. 21:52
드래그 앤 드롭 라이브러리

 

Source Code
https://github.com/kangilbin/React.js/tree/master/DragAndDrop

 


1. 라이브러리 설치

npm i react-beautiful-dnd

/* typscript */
npm i --save-dev @types/react-beautiful-dnd

 

( index .tsx )

<React.StrictMode>  // 삭제
   ...
</React.StrictMode>
  • React 18 버전에서의 StrictMode를 지원하지 않기 때문에 index파일에서 StrictMode를 지워야 한다.

react-beautiful-dnd 구성도

  • DragDropContext : 드래그 앤 드롭이 가능하게 하고자 하는 영역 감싼다.
  • Droppable : 어떠한 것을 드래그 앤 드롭할 수 있는 영역
  • Draggable : Droppable 영역 안에서 실제 드래그하는 영역

 

 

 


2. 드래그 앤 드롭 구현 예시

import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";

function App() {
  const onDragEnd = () => {};
  return (
    <DragDropContext onDragEnd={onDragEnd}>
      <div>
        <Droppable droppableId="one">
          {(provided, snapshot) => (
            <ul ref={provided.innerRef} {...provided.droppableProps}>
              <Draggable draggableId="first" index={0}>
                {(provided, snapshot) => (
                  <li
                    ref={provided.innerRef}
                    {...provided.draggableProps}
                    {...provided.dragHandleProps}
                  >
                    One
                  </li>
                )}
              </Draggable>
              <Draggable draggableId="second" index={1}>
                {(provided) => (
                  <li
                    ref={provided.innerRef}
                    {...provided.draggableProps}
                    {...provided.dragHandleProps}
                  >
                    Two
                  </li>
                )}
              </Draggable>
            </ul>
          )}
        </Droppable>
        {provided.placeholder}
      </div>
    </DragDropContext>
  );
}

export default App;
  • placeholder : 드롭되더라도 리스트의 사이즈를 그대로 유지시켜준다.
  • ref : beautiful-dnd가 HTML요소에 접근할 수 있도록 설정해 준다.
  • 무조건 Draggable의 key와 draggableId가 같아야 한다.

 

2-1) Droppable, Draggable : 자식 요소는 react 요소이면 안된다 함수여야 한다.

<Droppable droppableId="one">
      {(provided, snapshot) => (
          <Draggable draggableId="first" index={0}>
                {(provided, snapshot) => (
                	// 함수 방식
                 )}
      )}
</Droppable>
  • 첫 번째 param : provided를 가진다. 
  • 두 번째 param : snapshot를 가진다 card(Draggable), board(Droppable)의 색상을 변경할 때 사용 

 

 

2-2)  Droppable, Draggable의 provided

( Droppable Props )

 <ul ref={provided.innerRef} {...provided.droppableProps}> </ul>

 

( Draggable Props )

  <li
    ref={provided.innerRef}
    {...provided.draggableProps}
    {...provided.dragHandleProps}
  >
    One
  </li>
  • 모든 prop을 주면 드래그, 드롭, 애니메이션이 적용된다.

 

 

2-3) 특정 부위를 드래그 가능하게 하는 방법

<li ref={provided.innerRef} {...provided.draggableProps}>
  <span {...provided.dragHandleProps}>👌</span>
  Two
</li>
  • dragHandleProps : 해당 prop을 가지고 있는 요소에서만 드래그가 가능하다.

 

 


3. onDragEnd 

const onDragEnd = (args: any) => {
	console.log(args);
};
  • 드래그 앤 드롭 이후 발생하는 이벤트

  • destination : 이동하고자 하는 droppable과 인덱스 정보가 담겨있다.
  • source : 선택한 droppable과 인덱스가 정보가 담겨있다.

 

 

 


4. react memo (부분 렌더링) 활용하기 

드래그 앤 드롭 시 전부 렌더링 된다. react memo는 prop이 바뀌지 않는다면 컴포넌트를 렌더링 하지 않는다.
https://ko.reactjs.org/docs/react-api.html#reactmemo
import React from "react";

function DraggableCard({ toDo, index }: IDraggableCardProps) {
	...
}
export default React.memo(DraggableCard);
  • React.memo(DraggableCard) : prop이 변하지 않았다면 DraggableCard를 다시 렌더링 하지 않는다.

 

 

 


5. Snapshot을 이용한 색상 변경

5-1) Board 색상 변경 

...
interface IAreaProps {
  isDraggingOver: boolean;
  isDraggingFromThis: boolean;
}

const Area = styled.div<IAreaProps>`
  background-color: ${(props) =>
    props.isDraggingOver ? "pink" : props.isDraggingFromThis ? "red" : "blue"};
  flex-grow: 1;
  transition: background-color .3s ease-in-out;
`;

function Board({ toDos, boardId }: IBoardProps) {
  return (
    <Wrapper>
      <Title>{boardId}</Title>
      <Droppable droppableId={boardId}>
        {(provided, snapshot) => (
          <Area
            isDraggingOver={snapshot.isDraggingOver}
            isDraggingFromThis={Boolean(snapshot.draggingFromThisWith)}
            ref={provided.innerRef}
            {...provided.droppableProps}
          >
            {toDos.map((toDo, index) => (
              // key 와 draggableId는 무조건 같아야 한다.
              <DraggableCard key={toDo} toDo={toDo} index={index} />
            ))}
            {provided.placeholder}
          </Area>
        )}
      </Droppable>
    </Wrapper>
  );
}
export default Board;
  • isDraggingOver : board 위로 드래그해서 들어오고 있는지 알려준다.
  • draggingFromThisWith : 해당 board로부터 드래그를 시작했는지도 알려준다. 즉, 유저가 어떤 board를 떠난다면 그 board로 부터 drag를 시작했다는 뜻
  • draggingFromThis : 현재 board를 떠난다면 (결과 값은 draggableId 또는 undefind)

 

5-2) Card 색상 변경

...
const Card = styled.div<{ isDragging: boolean }>`
  padding: 10px 10px;
  border-radius: 5px;
  margin-bottom: 5px;
  background-color: ${(props) =>
    props.isDragging ? "tomato" : props.theme.cardBgColor};
`;
interface IDraggableCardProps {
  toDo: string;
  index: number;
}
function DraggableCard({ toDo, index }: IDraggableCardProps) {
  console.log(toDo, "has been rendered");
  return (
    <Draggable draggableId={toDo} index={index}>
      {(provided, snapshot) => (
        <Card
          isDragging={snapshot.isDragging}
          ref={provided.innerRef}
          {...provided.dragHandleProps}
          {...provided.draggableProps}
        >
          {toDo}
        </Card>
      )}
    </Draggable>
  );
}
export default React.memo(DraggableCard);

 

반응형