[React] Drag and Drop

찐새·2022년 8월 17일
1

React

목록 보기
9/21
post-thumbnail

Drag and Drop

React Beautiful DnD @hello-pangea/dnd

Install

  • npm i react-beautiful-dnd

    • 버전이 안 맞으면 npm i react-beautiful-dnd --legacy-peer-deps로 의존성을 무시하고 설치한다.

    • React 18 이상에서는 작동하지 않는다.

    • velog.io/@yonyas - --legacy--force 차이

    • ts 환경은 npm i --save-dev @types/react-beautiful-dnd를 설치해 타입을 알려준다.

  • React 18을 지원하는 @hello-pangea/dnd로 대체했다.

Basic Setting

DragDropContext

  • dnd(drag and drop)을 가능하게 하고 싶은 부분을 설정한다.
  • onDragEnd 함수와 자식 요소를 필요로 한다.
    • onDragEnd는 사용자의 드래그가 끝나는 시점을 알리는 역할이다.
import { DragDropContext } from "@hello-pangea/dnd";

function App() {
  const onDragEnd = () => {};
  return (
    <DragDropContext onDragEnd={onDragEnd}>
      <div></div>
    </DragDropContext>
  );
}

export default App;

Droppable

  • 아이템을 drop하는 영역이다.
  • 아이템이 여러 개일 수 있기 때문에 droppableId를 요구한다.
  • 자식 요소는 단순한 React Elements가 아닌 함수로 구성되어야 한다.
import { Droppable } from "@hello-pangea/dnd";

function App() {
  ...
  return (
    ...
        <Droppable droppableId="one">{() => <ul></ul>}</Droppable>
    ...
  );
}

export default App;

Draggable

  • 아이템을 drag하는 영역이다.
  • draggableIdindex, 자식 요소를 필요로 한다.
    • 자식 요소는 함수를 사용한다.
import { Draggable } from "@hello-pangea/dnd";

function App() {
  ...
  return (
      ...
        <ul>
          <Draggable draggableId="first" index={0}>
            {() => <li>One</li>}
          </Draggable>
          <Draggable draggableId="second" index={1}>
            {() => <li>Two</li>}
          </Draggable>
        </ul>
      ...
  );
}

export default App;

Usage

Droppable

  • innerRefdroppableProps를 요소에 제공해야 한다.

Draggable

  • innerRefdraggableProps, dragHandleProps를 요소에 제공해야 한다.
    • dragHandleProps는 드래그 가능한 특정 영역을 지정한다.
  • 드래그 시 영역이 무너지는 것을 방지하려면 Draggable 컴포넌트가 끝나는 지점 아래에 droppableProvided.placeholder를 추가한다.
import { DragDropContext, Droppable, Draggable } from "@hello-pangea/dnd";

const itemList = ["a", "b", "c", "d", "e", "f"];

function App() {
  const onDragEnd = () => {};
  return (
    <DragDropContext onDragEnd={onDragEnd}>
      <div>
        <Droppable droppableId="one">
          {(droppableProvided) => (
            <ul
              ref={droppableProvided.innerRef}
              {...droppableProvided.droppableProps}
            >
              {itemList.map((item, index) => (
                <Draggable key={item} draggableId={item} index={index}>
                  {(draggableProvided) => (
                    <li
                      ref={draggableProvided.innerRef}
                      {...draggableProvided.draggableProps}
                      {...draggableProvided.dragHandleProps}
                    >
                      {item}
                    </li>
                  )}
                </Draggable>
              ))}
              {droppableProvided.placeholder}
            </ul>
          )}
        </Droppable>
      </div>
    </DragDropContext>
  );
}

export default App;

onDragEnd

  • 움직인 itemList의 요소를 그 자리에 고정하려면 onDragEnd 함수를 이용해 배열를 수정해야 한다.
  • argsDropResult 형태를 가지고 있으며, 움직인 요소는 draggableIdsource로, 드랍한 자리는 destination으로 알 수 있다.
  • 위치가 바뀐 itemList는 다음 로직으로 재구성할 수 있다.
const itemList = ["a", "b", "c", "d", "e", "f"];

// "a"를 "index:3"으로 이동한다고 했을 때
itemList.splice(0, 1); // "a"의 index, 제거할 요소 개수
itemList.splice(3, 0, "a"); // 삽입할 index, 제거할 요소 개수, 삽입할 요소

console.log(itemList); // ['b', 'c', 'd', 'a', 'e', 'f']

참고 MDN - splice

  • 위 로직을 함수에 추가하면 요소를 옮긴 모양으로 배열이 변경된다.
const onDragEnd = ({ draggableId, destination, source }: DropResult) => {
  if (!destination) return;
  itemList.splice(source.index, 1);
  itemList.splice(destination?.index, 0, draggableId);
};

dnd Optimize

  • Draggable의 자식 요소가 움직이면 그것을 감싼 부모 요소의 상태가 모두 변경되기 때문에 React는 그 횟수만큼 re-rendering한다.
  • React.memoprop이 바뀌지 않는 한 컴포넌트를 렌더링하지 않게끔 한다.
    • Draggable 컴포넌트를 분리한 후 React.Memo로 감싼다.
// DraggableComponent.tsx

import React from "react";

function DraggableComponent({ item, index }) {
  return (
    <Draggable draggableId={item} index={index}>
      {(draggableProvided) => (
        <li
          ref={draggableProvided.innerRef}
          {...draggableProvided.draggableProps}
          {...draggableProvided.dragHandleProps}
        >
          {item}
        </li>
      )}
    </Draggable>
  );
}

export default React.memo(DraggableComponent);
  • 변화가 없는 요소의 불필요한 렌더링을 제거해 최적화할 수 있다.

참고

노마드 코더 - React JS 마스터클래스

profile
프론트엔드 개발자가 되고 싶다

0개의 댓글