프로젝트를 개발하면서 Drag & Drop 기능이 필요했는데 예전부터 써보고 싶었던 모듈인 react-beautiful-dnd를 사용해 DND를 개발해보았다.
타입스크립트 환경에서 개발했기 때문에 type까지 설치해주었다.
yarn add react-beautiful-dnd @types react-beautiful-dnd
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;
DragDropContext
컴포넌트에 바인딩된 onDragEnd
함수가 동작하며 최종적인 Drag & Drop에 대한 DOM을 그려주기 때문에 필수적으로 영역을 지정해주어야 한다.droppableId
를 props를 필수적으로 넣어야 하며 해당 id는 고유해야 한다.
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;
draggableId
라는 고유의 id가 반드시 있어야하며 이값은 string type이다.index
가 반드시 있어야하며 이는 내에서 의 인덱스와 일치한다.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,
});
<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>