React-Beautiful-DnD

Happhee·2022년 6월 15일
2

💙  React 💙

목록 보기
18/18
post-thumbnail
post-custom-banner

✨ 들어가기

react에서 drag and drop 기능을 가능하게 하는 라이브러리는 react-draggable , react-dnd, react-beautiful-dnd로 총 3가지가 있다.

npm-trend 를 보면 알 수 있듯이 react-draggable이 제일 사용률이 높고, react-dnd와 react-beautiful-dnd는 비슷하게 사용되고 있다.

🤔 여기서 잠깐!! 이들의 차이점은 무엇일까?

  • react-draggable
    드래그로 어떠한 아이템 간의 순서를 변경하는 것보다 윈도우안에서 어떠한 아이템을 드래그해서 위치만을 바꾸는 부분에서 강점이 있는 라이브러리이다.

  • react-dnd, react-beautiful-dnd
    드래그로 어떠한 아이템 간의 순서를 변경하는 것에 강점이 있는 라이브러리이다.
    여기서 react-dnd는 hover중일때 순서가 변경되는 애니메이션이나 위치 변경을 직접 정의해야 한다.
    반면, react-beautiful-dnd는 UI/UX나 퍼포먼스가 좋은 동작이 미리 정의되어 있다. 그래서 react-dnd보다 용량이 약 2배 많다.

직접 커스터마이징 하기 전에 react안에서의 drag and drop의 기능 구현을 해보고 싶어서 이번에는 react-beautiful-dnd를 공부하기로 하였다.


📄 React-Beautiful-DnD 적용하기

1️⃣ React-Beautiful-DnD 설치

React + TypeScript + react-beautiful-dnd 로 진행하였다.

// CRA 🖥
yarn create react-app RBD --template typescript
yarn add typescript @types/node @types/react @types/react-dom @types/jest

// rbd ✨
yarn add  react-beautiful-dnd @types/react-beautiful-dnd

타입스크립트를 사용하기에 타입도 함께 설치해주어야 한다.

2️⃣ strictMode 제거

react를 strictMode로 동작시키는 경우, 아래와 같이 라이브러리가 제대로 작동하지 않는다.

따라서 index.tsx에 선언되어 있는 React.strictMode를 제거해주어야 한다.

import ReactDOM from "react-dom/client";
import App from "./App";

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(
  // <React.StrictMode>
    <App />
  /* </React.StrictMode> */
);

3️⃣ Land 자체 작업 걸어주기

strictMode를 제거하였기 떄문에, 렌더링 검증을 하는 부분이 필요하다고 생각하였다.

따라서 아이템리스트를 drag 가능하게 하는 컴포넌트를 렌더링 이후에 나타나게 하도록 검증하는 DragLand.tsx를 App.tsx에서 불러와 사용하였다.

  • App.tsx
import DragLand from "../src/components/DragLand";
export default function App() {
  return (
    <>
      <DragLand />
    </>
  );
}
  • /components/DragLand.tsx
import { useState, useEffect } from "react";
import Drag from "./Drag";
export default function DragLand() {
  const [ready, setReady] = useState<Boolean>(false);
  useEffect(() => {
    setReady(true);
  }, []);
  return <div>{ready ? <Drag /> : null}</div>;
}

렌더링을 검사하는 ready 변수를 Boolean의 state로 만들고, 초기값으로 false를 주었다. 이후 컴포넌트 초기 렌더링 이후에 실행되는 useEffect를 사용하여 ready의 값을 true로 변환시켰다.

결과적으로 렌더링이 정상적으로 완료된다면 Drag컴포넌트가 불러와질 것이고, 그렇지 않다면 null값을 반환할 것이다.

4️⃣ Drag.tsx 불러오기

✨ import

Drag.tsx에서 react-beautiful-dnd를 사용하기 위한 코드는 아래와 같다.

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

✨ 아이템을 움직일 범위 지정하기

drag할 아이템 리스트들이 움직일 범위를 지정해주는 DragDropContext 컴포넌트를 제일 처음 지정해주어야 하고, 그 안에 Droppable 컴포넌트를 넣어 움직일 아이템의 리스트들의 범위를 생성한다.

<DragDropContext>
        <Droppable droppableId="todos">
          {(provided) => (
            <ul
              className="todos"
              {...provided.droppableProps}
              ref={provided.innerRef}
            >
                // ✨ 움직일 아이템 리스트들을 넣어주는 곳 ✨
            </ul>
          )}
        </Droppable>
      </DragDropContext>
❗️ 주의할 부분 ❗️
  • Droppable의 droppableId === provided를 return하는 html element의 className반드시 일치해야 한다.
  • provided를 return하는 element를 html이 아닌 component로 사용하고 싶다면 반드시 div태그를 사용해야 한다.
          {(provided) => (
            <div
              className="todos"
              {...provided.droppableProps}
              ref={provided.innerRef}
            >
                // ✨ 움직일 아이템 리스트들을 넣어주는 곳 ✨
                <TodoList>
                </TodoList>
            </div>
          )}

✨ Drag 컴포넌트 넣어주기

      <DragDropContext onDragEnd={handleChange}>
        <Droppable droppableId="todos">
          {(provided) => (
            <ul
              className="todos"
              {...provided.droppableProps}
              ref={provided.innerRef}
            > 👇
              {todos.map(({ id, title }, index) => (
                <Draggable key={id} draggableId={id} index={index}>
                  {(provided) => (
                    <li
                      ref={provided.innerRef}
                      {...provided.draggableProps}
                      {...provided.dragHandleProps}
                    >
                      //  ✨ 움직일 아이템 리스트들을 넣어주는 곳 ✨
                    </li>
                  )}
                </Draggable>
              ))}
            </ul>
          )}
        </Droppable>
      </DragDropContext>
❗️ 주의할 부분 ❗️
  • Draggable 의 draggableId === key반드시 일치해야 한다.
<Draggable key={id} draggableId={id} index={index}>
  • Droppable과 마찬가지로 html elemnent를 이용해 props로 넘겨야 한다.
    ❌ 이를 어길시 동작하지 않는다 ❌

✨ provided.placeholder 추가하기

아이템 컴포넌트를 이동할 때, 원하는 자리에 올바르게 위치하도록 하기 위해서placeholder 함수를 추가한다.

      <DragDropContext onDragEnd={handleChange}>
        <Droppable droppableId="todos">
          {(provided) => (
            <ul
              className="todos"
              {...provided.droppableProps}
              ref={provided.innerRef}
            >
              {todos.map(({ id, title }, index) => (
                <Draggable key={id} draggableId={id} index={index}>
                  {(provided) => (
                    <li
                      ref={provided.innerRef}
                      {...provided.draggableProps}
                      {...provided.dragHandleProps}
                    >
                      //  ✨ 움직일 아이템 리스트들을 넣어주는 곳 ✨
                    </li>
                  )}
                </Draggable>
              ))}
              👇
              { provided.placeholder }
            </ul>
          )}
        </Droppable>
      </DragDropContext>

만약 이를 사용하지 않으면, 정상적으로 작동하긴 하지만 placeholder가 없다는 에러 메세지가 뜬다.

따라서 이를 반드시 작성해주도록 하자.

✨ Drag로 이동한 아이템을 Drop 하는 함수 작성하기

onDragEnd 이벤트로 사용할 mouse drag로 이동한 아이템을 drop하여 drag가 끝났을 때의 결과를 저장하는 함수를 작성해야 한다.

  const handleChange = (result: DropResult) => {
    
    if (!result.destination) return;
    
    const items = [...todos];
    const [reorderedItem] = items.splice(result.source.index, 1);
    items.splice(result.destination.index, 0, reorderedItem);

    setTodos(items);
  };

result.destination(결과)가 존재하지 않으면 그냥 반환하고
존재한다면 원본 리스트를 복사하여 splice메서드를 사용해
선택한 아이템을 가져와 이를 결과의 인덱스에 삽입시킨 복사 리스트로 state를 변경한다.

🖥 Happhee - RBD 구현


📚 학습할 때, 참고한 자료 📚

profile
즐기면서 정확하게 나아가는 웹프론트엔드 개발자 https://happhee-dev.tistory.com/ 로 이전하였습니다
post-custom-banner

0개의 댓글