몽환화

[React] react-beautiful-dnd 간단하게 시작해보기 본문

Front/React

[React] react-beautiful-dnd 간단하게 시작해보기

hyeii 2024. 7. 13. 14:49

사실 이번에는 물고뜯기까지는 못하고 앙 물어보기만 해서 물고뜯기라고 못함 그냥 시작해보자고 

 

모종의 이유로 드래그앤드롭을 활용할 일이 매우 많아졌습니다.. 그리고 주로 사용하는 라이브러리가 바로 이

react-beautiful-dnd 였기에 공부를 해보았어요

사실 냅다 적용부터 해보고 뒤늦게 공부중;

 

이럴줄 알았으면 토이플젝때 투두리스트 내가맡을걸!

 

아무튼 시작

https://github.com/atlassian/react-beautiful-dnd

 

GitHub - atlassian/react-beautiful-dnd: Beautiful and accessible drag and drop for lists with React

Beautiful and accessible drag and drop for lists with React - atlassian/react-beautiful-dnd

github.com

 

https://www.npmjs.com/package/react-beautiful-dnd/v/11.0.2

 

react-beautiful-dnd

Beautiful and accessible drag and drop for lists with React. Latest version: 13.1.1, last published: 2 years ago. Start using react-beautiful-dnd in your project by running `npm i react-beautiful-dnd`. There are 2012 other projects in the npm registry usin

www.npmjs.com

이 라이브러리는 무려 아틀라시안에서 만든것이다

믿고 써도 된다는 뜻!

 

npm i react-beautiful-dnd @types/react-beautiful-dnd

 

몇번 써보면 금방 적응될 것 같긴 한데 처음 건드리기가 상당히 복잡하고 어렵다

그래서 정말 간단한 예제를 들어볼 예정!

 

https://github.com/LeeHyungGeun/react-beautiful-dnd-kr

한국어로 번역해주신 Docs도 있당! 

 

먼저 드래그앤드롭이 뭐냐

-드래그

-드롭

두가지가 필요하다

 

원하는 요소를 먼저 집어서 옮기고 내려놓는 과정이 필요하다

이걸 beautiful-dnd에서 적용하려면

집어서 옮기고 => draggable

내려놓는 => droppable

이 되겠다.

 

여기서 유의할 점은 만약 투두리스트같은 일반 리스트 dnd를 할거라면

결국 집는 요소나 내려놓는 요소나 그 위치가 그 위치라는거

droppable한 곳에서 다시 집어서 옮겨야하니 draggable 해야 한다

물론 이런건 자신이 어떤 드래그앤드롭을 구현하냐에 따라 달라지겠지만!

 

아무튼 저 두 개념을 알고 시작하면 되겠다.

 

그리고 중요한 요소 세개

 

DragDropContext

우리가 드래그앤드롭을 할 전체 공간을 감싸줘야한다. 익숙한 context의 등장.

사용할 컴포넌트의 전체를 감싸주면 된다

이 내부에 앞서 말한 Draggable과 Droppable을 적용하면 된다

 

Droppable

앞서 설명한 요소를 내려놓을 수 있는 공간, 내려놓은 곳에서 다시 집어서 옮기려면 다음의 Draggable을 포함한다.

 

Draggable 

집어서 옮길 수 있는 요소들. 

 

이렇게 크게 세가지 요소를 알면 된다. 

 

 

조금 더 자세히 알아보자면

DragDropContext

onDragEnd : DragDropContext의 필수함수

말 그대로 드래그가 종료되었을 때 발생할 일을 담아주어야 한다.

react-beautiful-dnd will throw an error if a onDragEnd prop is not provided

This function is extremely important and has an critical role to play in the application lifecycle. This function must result in the synchronous reordering of a list of Draggables

 

onDragStart

onDragEnd처럼 필수함수는 아니지만 드래그가 시작될 때 발생할 일을 담아줄 수 있다.

 

 

Droppable

<Droppable /> components can be dropped on by a <Draggable />. They also contain <Draggable />s. A <Draggable /> must be contained within a <Droppable />.

droppableId

스트링으로 지정해야하고 드롭 가능한 곳을 구분해주기 위한 id이다

provided 

provided.innerRef 를 ref에 걸어준다. 드래그앤드롭은 결국 돔을 조작하는 일이기 때문에 설정해주어야 한다. 그래야 얘를 바로 찾아서 조작해줄 수 있다

provided.droppableProps 이것두

provided.placeholder drop될 때 영역을 미리 잡아놓는 역할을 한

snapshot 필수요소는 아니고 드래그앤드롭할때 스타일을 설정해줄 수 있다.

 

Draggable

draggableId

마찬가지로 드래그할 영역을 구분할 id

index

반드시 순서대로 입력되어야함!

provided

provided.innerRef 이건 위에랑 똑같고

provided.draggableProps 이것도 등록해주기위해 필요하다

provided.dragHandleProps

snapshot : 마찬가지

 

 

 

그럼 대충 요소들의 역할을 알겠고 이걸 어떻게 적용할건데?

이 드래그앤드롭은 결국 단순히 드래그하고 드롭하고 끝나는게 아니라 드래그앤드롭을 통해서 우리에게 보여지는 요소들의 위치, 순서를 바꿔주어야 한다. 이것이 onDragEnd함수에 담길 내용이다!

 

그걸 어떻게 담냐 

간단한 예시를 보자

 

	<DragDropContext onDragEnd={onDragEnd}>
        <Droppable droppableId="01" key="01">
          {(provided, snapshot) => (
            <DraggableBox ref={provided.innerRef} {...provided.droppableProps}>
              {list.map((babo, index) => (
                <Draggable key={babo.id} draggableId={babo.id} index={index}>
                  {(provided) => (
                    <DraggablePoint
                      ref={provided.innerRef}
                      {...provided.dragHandleProps}
                      {...provided.draggableProps}
                    >
                      {babo.content}
                    </DraggablePoint>
                  )}
                </Draggable>
              ))}
              {provided.placeholder}
            </DraggableBox>
          )}
        </Droppable>
      </DragDropContext>

 

위의 내용을 적용하면 이렇게 표시할 수 있다 DraggableBox랑 DraggablePoint는 그냥 내가 스타일 컴포넌트 먹인거고

Droppable요소 안에 Draggable이 들어가있다는걸 확인하자

 

물론 이렇게 보면 뭔지 모르겠다... babo가 뭔냐면

 

자 바보 세명이 있다 쿄쿄

 const [list, setList] = useState([
    { id: "babo1", content: <h3>바보1</h3> },
    { id: "babo2", content: <h3>바보2</h3> },
    { id: "babo3", content: <h3>바보3</h3> },
  ]);
  
  const onDragEnd = (result: DropResult) => {
    if (!result?.destination) return;

    console.log(result);

    const sourceIndex = result.source.index;
    const destinationIndex = result.destination.index;
    const newList = [...list];
    const pickedBabo = newList[sourceIndex];
    newList.splice(sourceIndex, 1);
    newList.splice(destinationIndex, 0, pickedBabo);
    setList(newList);
  };

 

useState를 사용해 초기 상태를 설정해준다

결국 얘네도 상태관리가 필요한 놈들이니까

다만 우리에게 필요한 id를 설정하고, 각 바보들의 컨텐츠를 돔 요소로 담아줘보자

 

onDragEnd 함수를 찬찬히 살펴보면 얘는 DropResult라는 타입으로 result를 받는데

result는 크게 destinationsource를 받는다.

result.destination이 없다는건 목적지가 없다 => 요상한 곳으로 드래그를 시켰다는 뜻이니 return시켜 없던일로 만든다

즉 초기 상태로 돌려놓는다.

 

..콘솔은 테스트한다고 그냥 찍어본거고

이렇게 요소들을 담고있다

draggableId와 destination에 해당하는 droppableId, index를 모두 갖고있으니 이것들을 통해서 드래그된 이후의 액션을 설정하면 된다.

const sourceIndex = result.source.index;
const destinationIndex = result.destination.index;

 

result.source 는 말 그대로 소스. 내가 드래그를 해 온 요소를 가리킨다.

result.destination 은 목적지. 내가 드래그앤드롭한 목적지를 가리킨다. 

각 요소의 index를 먼저 뽑아오자. 

우리의 목적은 해당 두 요소의 위치를 바꿔주는거다!

 

const newList = [...list];
const pickedBabo = newList[sourceIndex];
newList.splice(sourceIndex, 1);
newList.splice(destinationIndex, 0, pickedBabo);

 

초기 list 상태를 복사하고, 드래그를 해 온 요소를 pickedBabo에 저장한다.

복사한 리스트에서 sourceIndex부분을 삭제한 뒤 저장한 pickedBabo를 붙여넣는다.

그럼 두 위치가 바뀌었겠지?

이걸 list에 다시 저장하면 되는거다.

 

이게 기본적인 투두리스트 같은 예제에서 드래그앤드롭을 하는 예시일거다.

 

 

 

코드 전문

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

export const Dnd = () => {
  const [list, setList] = useState([
    { id: "babo1", content: <h3>바보1</h3> },
    { id: "babo2", content: <h3>바보2</h3> },
    { id: "babo3", content: <h3>바보3</h3> },
  ]);
  const onDragEnd = (result: DropResult) => {
    if (!result?.destination) return;

    console.log(result);

    const sourceIndex = result.source.index;
    const destinationIndex = result.destination.index;
    const newList = [...list];
    const pickedBabo = newList[sourceIndex];
    newList.splice(sourceIndex, 1);
    newList.splice(destinationIndex, 0, pickedBabo);
    setList(newList);
  };

  return (
    <div>
      <DragDropContext onDragEnd={onDragEnd}>
        <Droppable droppableId="01" key="01">
          {(provided, snapshot) => (
            <DraggableBox ref={provided.innerRef} {...provided.droppableProps}>
              {list.map((babo, index) => (
                <Draggable key={babo.id} draggableId={babo.id} index={index}>
                  {(provided) => (
                    <DraggablePoint
                      ref={provided.innerRef}
                      {...provided.dragHandleProps}
                      {...provided.draggableProps}
                    >
                      {babo.content}
                    </DraggablePoint>
                  )}
                </Draggable>
              ))}
              {provided.placeholder}
            </DraggableBox>
          )}
        </Droppable>
      </DragDropContext>
    </div>
  );
};

const DraggableBox = styled.div`
  display: flex;
  flex-direction: column;
`;

const DraggablePoint = styled.div`
  display: flex;
  justify-contents: center;
  height: 50px;
`;

 

 

 

만약 드래그앤드롭이 잘 되지 않고 항목이 선택이 잘 안된다면 리액트의 strickmode를 해제해보자

 

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { RecoilRoot } from "recoil";

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

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

 

dnd를 사용한 진짜 간단한 예제이다 이제 이걸 가지고 다양하게 변형해 적용하면 된다!

예를들면 내가 지금 하고있는 컨텐츠 제작이라든ㄱ ㅏ,,,등등

'Front > React' 카테고리의 다른 글

[React] Suspense 사용해보기  (0) 2025.04.20
[React] Recoil 뜯어보기  (0) 2024.05.22
[React] useLayoutEffect, useDebugValue  (0) 2024.05.06
[React] useImperativeHandle 물고뜯기  (0) 2024.05.06
[React] useReducer 물고뜯기  (0) 2024.05.03