react-beautiful-dnd로 드래그 앤 드롭 구현하기 (특정 영역에만 드래그 적용하게)

Jin·2022년 3월 4일
0

React

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

코드는 타입스크립트 기준입니다.

요구조건은 2가지였습니다.

  • 드래그 앤 드롭이 되어야 한다.
  • 지정된 영역에서 드래그하였을 때만 드래그가 되어야 한다.

1. 라이브러리 다운로드

먼저, react-beautiful-dnd 라이브러리를 다운로드합니다.

yarn add react-beautiful-dnd
yarn add -D @types/react-beautiful-dnd

2. DragDropContext 태그 삽입

DragDropContext로 드래그 앤 드롭을 인식할 수 있는 영역을 지정합니다.
어플리케이션을 감싸는 형태도 좋다고 하지만 이 프로젝트에서는 특정 페이지에서만 dnd를 사용하므로
페이지의 최상단을 아래와 같이 감싸주었습니다.

<DragDropContext onDragEnd={handleDragEnd}>
      ...
</DragDropContext>

onDragEnd는 드롭했을 때 발생하는 이벤트로 밑에서 정의하겠습니다.

3. Droppable 태그 삽입

<Droppable droppableId="fields">
   {(provided) => (
     <FormList {...provided.droppableProps} ref={provided.innerRef}>
       {formList.map((form, index) => (
             <FormItem>
               <Form
                 state={form}
                 onChange={onChange}
                 onRemove={removeForm}
               />
             </FormItem>
           )}
       ))}
     </FormList>
   )}
 </Droppable>

위와 같이 Droppable 태그로 드래그 앤 드롭할 영역을 감싸주고 droppableId를 지정해주고 ul에 해당하는 FormList에 ref와 props를 보내줍니다.

그래야 어떤 노드에서 이벤트가 발생되는지 추적이 가능하다고 합니다.

4. Draggable 태그 삽입

이제 드래그할 요소들을 감싸주는 형태로 Draggable 태그를 삽입합니다.

<Droppable droppableId="fields">
  {(provided) => (
    <FormList {...provided.droppableProps} ref={provided.innerRef}>
      {formList.map((form, index) => (
        <Draggable
          key={form.key}
          draggableId={form.key}
          index={index}
        >
          {(provided) => (
            <FormItem
              ref={provided.innerRef}
              {...provided.draggableProps}
            >
              <Form
                state={form}
                onChange={onChange}
                onRemove={removeForm}
                provided={provided}
              />
            </FormItem>
          )}
        </Draggable>
      ))}
      {provided.placeholder}
    </FormList>
  )}
</Droppable>

드래그되는 요소에 ref와 draggableProps를 props로 보내줍니다.

만약 드래그할 수 있는 영역이 드래그되는 요소와 동일하다면 props에 {...provided.dragHandleProps}를 추가하면 됩니다. 이 프로젝트에서는 특정 영역에서 드래그하는 경우에만 적용되어야 하므로 Form 컴포넌트에 Props로 provided를 보내주었습니다.

  • provided.innerRef : droppable이 올바르게 작동하려면, provided.innerRef를 ReactElement에서 가능한 최상위 DOM 노드에 바인딩해야 합니다. DOM 노드를 조회하기 위해 ReactDOM을 사용할 필요가 없도록 하기 위해 이렇게 합니다.
  • provided.placeholder : 드래그하는 동안 필요에 따라 에 공간을 만드는 데 사용됩니다. 이 공간은 사용자가 홈 목록이 아닌 목록 위로 끌 때 필요합니다. 참조를 제공한 구성 요소 내부에 자리 표시자를 넣어야 합니다. 자체의 크기를 늘려야 합니다.
  • droppableProps : Droppable 요소에 적용해야 하는 속성이 포함된 개체입니다. provided.innerRef를 적용한 동일한 요소에 적용해야 합니다. 현재 스타일링 및 조회에 사용하는 데이터 속성이 포함되어 있습니다.

5. 특정영역에서 드래그하는 경우에만 되도록 dragHandleProps 적용하기

위에서 Form 컴포넌트에 props로 provided를 보내주었습니다.
이제, Form 컴포넌트에서 이 provided를 받아서

...
<Drag {...provided.dragHandleProps}>
  <CgArrowsVAlt></CgArrowsVAlt>
</Drag>
...

이런 식으로 드래그 이벤트를 발생시키고 싶은 영역에 props를 추가합니다.

6. onDragEnd 이벤트 처리하기

여기까지 했다면 드래그는 되지만 드롭했을 시 다시 원래대로 돌아갈 것입니다.
상태와 연동이 되지 않아서 그렇습니다.

const handleDragEnd = (result: DropResult) => {
  if (!result.destination) {
    return;
  }
  
  const currentList = [...formList];
  const draggingItemIndex = result.source.index;
  const dropItemIndex = result.destination.index;
  // 드래그한 요소를 배열에서 삭제
  const removeForm = currentList.splice(draggingItemIndex, 1);
  // 드롭한 위치에 드래그한 요소를 추가
  currentList.splice(dropItemIndex, 0, removeForm[0]);
  setFormList(currentList);
};

저는 formList라는 배열 형태의 상태를 선언하여 map 메서드를 통해 렌더링하고 있었습니다.
따라서, formList를 복사하여 드래그하는 요소의 index와 drop한 위치의 index를 통해 해당 요소를 삭제하고 그것을 드롭한 위치에 삽입하여 상태 변경하는 식으로 처리하였습니다.

  • source에는 현재 드래그 중인 아이템의 정보가 담깁니다.
    • source: {droppableId: "fields", index: 1}
  • destination에는 드롭된 아이템의 정보가 담깁니다.
    • destination: {droppableId: "fields", index: 3}

맨 위의 if문은 만약

<Droppable droppableId="tags" direction="horizontal">
...

이런 식으로 수평적으로 드래그 앤 드롭을 하는 경우에 수직으로 드래그해서 드롭해버렸을 경우 드래그 컨테이너 영역을 벗어나게 되어서 destination에 아무 것도 담기지 않아 에러가 발생하는 경우를 예방하지 위해서입니다.

참고

https://velog.io/@yhe228/React-Beautiful-DnD

profile
배워서 공유하기
post-custom-banner

0개의 댓글