react-beautiful-dnd를 사용해 drag & drop 컴포넌트 구현하기

JJ·2022년 9월 15일
0

경험

목록 보기
3/4
post-thumbnail
post-custom-banner

프로젝트를 개발하면서 Drag & Drop 기능이 필요했는데 예전부터 써보고 싶었던 모듈인 react-beautiful-dnd를 사용해 DND를 개발해보았다.

설치

타입스크립트 환경에서 개발했기 때문에 type까지 설치해주었다.

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

라이브러리 이해

DragDropContext

  • 드래그앤드롭을 사용하기 위한 영역을 로 래핑하여야 한다.
  • onDragEnd props가 필수이기 때문에 onDragEnd 함수를 원형만 작성해준다.
import React, { useState } = 'react';
import { DragDropContext } from 'react-beautiful-dnd';
import TodoList from './components/TodoList';
import { Todo } from '../models/todo';


const App: React.FC = () => {
  
  const [todos, setTodos] = useState<Todo[]>([]);
  
  const onDragEnd (e:any)=>{
   // 나중에 작성 
  }
  
  return (
    <DragDropContext onDragEnd={onDragEnd}>
      <div className='app'>
        <TodoList todos={todos}/>
      </div>
    </DragDropContext>
  );
}

export default App;

Droppable

  • Drop이 발생하는(가능한) 영역이다.
  • 이 영역에서 Item을 Drop할 경우 DragDropContext 컴포넌트에 바인딩된 onDragEnd 함수가 동작하며 최종적인 Drag & Drop에 대한 DOM을 그려주기 때문에 필수적으로 영역을 지정해주어야 한다.
  • droppableId를 props를 필수적으로 넣어야 하며 해당 id는 고유해야 한다.
  • 컴포넌트는 ReactNode를 리턴하는 함수로 작성하여야한다.
  • 위 함수는 (provided,snapshop) =>ReactNode 형태의 함수이다.
  • provided.droppableProps 객체는 에 필요한 프로퍼티들이 담겨 있다.
  • provided.innerRef를 바인딩한 요소에 적용해야 한다.
  • provided.placeholder는 드래그가 일어나는 도중 내에 빈 공간을 만들기 위해 사용된다.
  • provided.innerRef를 바인딩한 요소 내부에 사용해야 한다.
import { Droppable } from 'react-beautiful-dnd';
import TodoItem from './TodoItem';
import { Todo } from '../models/todo';

interface Props {
  todos: Todo[];
  completed: Todo[];
}

const TodoList: React.FC = ({todos, completed}) => {
  return (
     <Droppable droppableId='todos'>
      		{(provided) => (
          	<div 
              ref={provided.innerRef} 
              {...provided.droppableProps}
            >
              <ul className='todo-list'>
              	{todos.map(todo, index) => (
                	<TodoItem 
                    index={index}
                    todo={todo}
                    key={todo.id}
                  />
                )}
                {provided.placeholder}
              </ul>
            </div>
          )}
     </Droppable>
  )
}

export default TodoList;

Draggable

  • 은 Drag가 일어나는 요소들로 래핑한다.
  • draggableId라는 고유의 id가 반드시 있어야하며 이값은 string type이다.
  • index가 반드시 있어야하며 이는 내에서 의 인덱스와 일치한다.
  • Draggable 컴포넌트의 children은 ReactNode를 return하는 함수로 작성하여야한다.
  • 위 함수는 (provided,snapshop) =>ReactNode 형태의 함수이다.

onDragEnd

  • 에 바인딩된 onDragEnd 함수는 Droppable 컴포넌트에서 Drop이 발생했을 시 실행된다.
  • onDragEnd는 첫번째 인자로 드롭한 결과가 담긴 DropResult를 받는다.
  • onDragEnd 함수에서 드래그앤드롭 이후 그 결과를 가지고 리스트를 재 정렬하는 로직을 장성하는데 쓰인다.
  import { DragDropContext, DropResult } from 'react-beautiful-dnd';
...

const App: React.FC = () => {
  
  const [todos, setTodos] = useState<Todo[]>([]);
  
  // 드래그가 끝났을 때의 동작을 지정해주는 함수
  const onDragEnd = (result: DropResult) => {
    const { source, destination } = result;
    
    // 드롭이 droppable 밖에서 일어났을 경우 바로 return
    if (!destination) return;
  
    // 드래그가 발생한 위치와 드롭이 발생한 위치가 같을 경우 바로 return
    if (source.droppableId === destination.droppableId && source.index === destination.index) return;
    
    let arr: Todo;
    let newTodo = todos;
    
    if (source.droppableId === 'todos') {
      arr = newTodo[source.index];
      newTodo.splice(source.index, 1);
    }
    
    if (destination.droppableId === 'todos') {
      newTodo.splice(destination.index, 0, {...arr});
    }
  
    setTodos(newTodos);
  }
  
  return (
    <DragDropContext onDragEnd={onDragEnd}>
      <div className='app'>
        <TodoList todos={todos} completed={completed}/>
      </div>
    </DragDropContext>
  );
}

  

드래그 중일 때 스타일 추가하기

  • 먼저 아이템의 스타일을 지정해준다.
  const getItemStyle = (isDragging: boolean, draggableStyle: any) => ({
  // some basic styles to make the items look a bit nicer
  userSelect: "none",
  // change background colour if dragging
  background: isDragging ? "#363F4D" : "#313131",
  ...draggableStyle,
});
  • snapshot의 isDragging 옵션으로 드래그 중인지 아닌지 판단한다.
       <Droppable droppableId='todos'>
      		{(provided ,snapshot) => (
          	<div 
              ref={provided.innerRef} 
              {...provided.droppableProps}
            >
              <ul className='todo-list'>
              	{todos.map(todo, index) => (
                	<TodoItem 
                    index={index}
                    todo={todo}
                    key={todo.id}
                    style={getItemStyle(snapshot.isDragging, provide.draggableProps.style)}
                  />
                )}
                {provided.placeholder}
              </ul>
            </div>
          )}
     </Droppable>
post-custom-banner

0개의 댓글