프론트개발을 하다보면 드래그 드롭이 UX적으로 적합 할 때가 종종 생깁니다.
허나 결과물은 간단한데 기능을 구현하는 과정은 전혀 간단하지 않기때문에 보일러플레이트를 사용해봅시다.
# npm
$ npm i -s react-beautiful-dnd
# yarn
$ yarn add react-beautiful-dnd
# typescript
npm i -s @types/react-beautiful-dnd
yarn add @types/react-beautiful-dnd
<DragDropContext>
로 래핑해야합니다....
//Drag가 시작되면 호출 될 함수
const onDragStart = () => {};
//Drag가 끝나면(Drop) 호출 될 함수
const onDragEnd = () => {};
return getLayout(
<DragDropContext onDragStart={onDragStart} onDragEnd={onDragEnd}>
<App />
</DragDropContext>
);
...
이제 Drag&Drop이 적용 될 요소의 범위를 설정해줄차례입니다.
기본적으론 원하는 요소범위 만큼 <Dropable>
을 래핑하면 됩니다.
<DragDropContext onDragStart={()=>{}} onDragEnd={()=>{}}>
<Droppable droppableId="todos">
{({ droppableProps, innerRef, placeholder }) => (
<ul className="todos" {...droppableProps} ref={innerRef}>
{todos.map(({ id, title }, idx) => (
<li>{title}</li>
))}
{placeholder}
</ul>
)}
</Droppable>
</DragDropContext>
provided.draggableProps
객체는 <Draggable>
의 드래그 동작에 필요한 프로퍼티들이 담겨 있습니다. provided.innerRef
를 바인딩한 요소에 적용해야 합니다.
provided.dragHandleProps
는 <Draggable>
전체를 드래그하기 위한 핸들입니다.
이제 Drop범위안의 원하는 요소를 Drag가능하게 만들어 봅시다.
<DragDropContext onDragStart={()=>{}} onDragEnd={()=>{}}>
<Droppable droppableId="todos">
{({ droppableProps, innerRef, placeholder }) => (
<ul className="todos" {...droppableProps} ref={innerRef}>
{todos.map(({ id, title }, index) => (
<Draggable key={id} draggableId={id} index={index}>
{(provided) => (
<li
ref={provided.innerRef}
{...provided.dragHandleProps}
{...provided.draggableProps}
>
{title}
</li>
)}
</Draggable>
))}
{placeholder}
</ul>
)}
</Droppable>
</DragDropContext>
<Draggable>
이 정상적으로 동작하기 위해서는 draggable로 간주될 ReactElement에 provided.innerRef
를 바인드 해야합니다.
provided.draggableProps
객체는 <Draggable>
의 드래그 동작에 필요한 프로퍼티들이 담겨 있습니다.
provided.dragHandleProps
는 <Draggable>
전체를 드래그하기 위한 핸들입니다.
드롭 후 상태를 저장하고 싶다면 <DragDropContext>
의 onDragEnd
를 이용합니다.
const [loading, setLoading] = React.useState<boolean>(true);
const [todoList, setTodoList] = React.useState(todos);
useEffect(() => {
setLoading(false);
}, []);
const handleDrop = (result: DropResult) => {
// 목적지가 없다면 ㅂㅂ
if (!result.destination) return;
// 현재 리스트
const items = [...todoList];
// 드레그 중인 인덱스정보 반환과 동시에 리스트에서 잠시 삭제
const [reorderedItem] = items.splice(result.source.index, 1);
// 목적지 인덱스에 위치에 드롭한 정보를 넣는다.
items.splice(result.destination.index, 0, reorderedItem);
setTodoList(items);
};
return (
<>
{!loading && (
<DragDropContext onDragEnd={handleDrop}>
<Droppable droppableId="todos">
{({ droppableProps, innerRef, placeholder }) => (
<ul className="todos" {...droppableProps} ref={innerRef}>
{todoList.map(({ id, title }, index) => (
<Draggable key={id} draggableId={`ddas` + id} index={index}>
{(provided) => (
<li
ref={provided.innerRef}
{...provided.dragHandleProps}
{...provided.draggableProps}
>
{title}
</li>
)}
</Draggable>
))}
{placeholder}
</ul>
)}
</Droppable>
</DragDropContext>
)}
</>
);