react-beautiful-dnd는 3개의 요소로 이루어져있습니다.
- DragDropContext
- 드래그를 사용할 영역을 의미합니다
- contextAPI의 Provider같은 느낌입니다. (감싸주는 느낌..?)
- Droppable
- Drag해서 놓을 Drop할 영역입니다.
- Draggable을 감싸서 사용합니다.
- Draggable
- Drag 할 Item 입니다.
- 우리가 이용할 Item컴포넌트를 Draggable로 감싸주면 됩니다.
DragDropContext
DragDropContext는 3개의 prop이 있습니다.
- onDragStart
- Darg를 시작할 때 (Drag 할 Item을 클릭하고 움직이기 시작할 때) 호출
- onDragEnd
- onDragUpdate
Droppable
1. Droppable에는 droppabledId 라는 props가 있습니다.
해당 Id는 Context 안에서 유일해야합니다. (영역을 구분지어야 하기 때문)
Droppable에서 사용할 Draggable에 해당하는 자식 컴포넌트는 Element를 받는게 아닌
함수 형태로 받아야 합니다.
<Droppable droppableId={list.id}>
{(provided: any) => (
<div>
{items.map((item, i) => (
<Item key={item.id} item={item} index={i} />
))}
{provided.placeholder}
</div>
)}
</Droppable>
provided에는 droppable에 사용할 props가 모여있습니다.
1. data-rbd-droppable-context-id
2. data-rbd-droppable-id
직접 쓰면..
{(provided: any) => {
return (
<div
data-rbd-droppable-context-id={
provided.droppableProps["data-rbd-droppable-context-id"]
}
data-rbd-droppable-id={provided.droppableProps["data-rbd-droppable-id"]}
ref={provided.innerRef}
>
사실 이렇게 쓰고싶은사람은 없으니.
{(provided: any) => {
return (
<div {...provided.droppableProps} ref={provided.innerRef} >
스프레드를 씁시다.
2. 앗. ref도 지정해줘야합니다.
3. 마지막으로 placeholder 라는 프로퍼티가 있습니다.
droppable에 변화가 생긴다면 해당 부분을 처리해주는 역할을 합니다.
<Droppable droppableId={list.id}>
{(provided: any) => {
return (
<div {...provided.droppableProps} ref={provided.innerRef}>
<>
{items.map((item, i) => (
<Item key={item.id} item={item} index={i} />
))}
{provided.placeholder}
</>
</div>
);
}}
</Droppable>
Draggable
1. Draggable은 draggableId와 index가 필수입니다.
Item 컴포넌트를 Drag가 가능하게 하려면 Draggable로 감싸야합니다.
Droppable처럼 함수 형태로 작성해야합니다.
const Item = ({ item, index }) => {
<Draggable draggableId={item.id} index={index}>
{(provided: any) => {
return (
<div
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
>
{item.content}
</div>
);
}}
</Draggable>
}
draggableProps, dragHandleProps, 그리고 ref까지 작성 해줘야합니다.
draggableProps : Drag 가능한 요소에 적용되는 속성을 나타내며, 해당 요소의 이벤트 리스너 및 기타 속성을 정의할 수 있습니다.
(context에서 작성한 onDragStart , onDragEnd 같은 친구들..)
dragHandleProps : Drag 액션을 처리하는 드래그 핸들에 적용되는 속성을 나타내며, Drag가 발생하는 영역을 정의할 수 있습니다.
onDrag 함수들에 대해
{
draggableId: 'itemList',
type: 'TYPE'
reason: 'DROP',
source: {
droppableId: 'item1',
index:0,
},
destination: {
droppableId: 'item1',
index:1,
},
}
draggableId : Drag 한 객체의 id
source : Drag가 시작된 위치 Droppable의 Id와 시작할 때 item의 index
destination : Drag가 끝나는 위치 Droppable의 Id와 끝날때 Item의 index Droppable의 밖에 드래그 할 경우 null
source와 destination을 이용해야합니다.
const onDragEnd = (result: DropResult) => {
const { destination, source, draggableId } = result;
if (!destination) return;
if (destination.droppableId === source.droppableId && source.index === destination.index)
return;
const newItemIds = Array.from(column.itemIds);
//시작지점 source의 아이탬의 index를 잘라냅니다 (위치를 바꿔주기위해)
newItemIds.splice(source.index, 1);
//아이탬의 도착지 destination의 index 위치에 draggableId를 추가합니다 (잘라낸 아이탬의 id)
newItemIds.splice(destination.index, 0, draggableId);
//바뀐 newItemIds를 갱신
const newColumn = {
...column,
itemIds: newItemIds,
};
//갱신한 값을 Data를 가져와서 바꿔줍니다
const newData = {
...data,
columns: {
...data.columns,
[newColumn.id]: newColumn,
},
};
setData(newData);
}
다른 Droppable에도 이동하기
처음엔 어떻게 하는건지.. 어렵게 생각했었는데..
크게 다르지 않았습니다.
기존소스를 이용하면서 바뀐부분은 주석!
const onDragEnd = (result: DropResult) => {
const { destination, source, draggableId } = result;
if (!destination) return;
if (destination.droppableId === source.droppableId && source.index === destination.index)
return;
const sourceColumn = data.columns[source.droppableId]; // 출발지의 droppable정보
const destinationColumn = data.columns[destination.droppableId];// 도착지점의 droppable정보
if(source.droppableId === destination.droppableId){
//기존소스
/*column 을 sourceColumn 으로 변경
어짜피 시작과 끝이 같은 영역일 경우에만 동작하므로
source나 destination 아무거나 사용해도 괜찮습니다.
그 영역의 정보를 가지고 오는것이 중요
*/
const newItemIds = Array.from(sourceColumn.itemIds);
newItemIds.splice(source.index, 1);
newItemIds.splice(destination.index, 0, draggableId);
//...column 을 ...sourceColumn 으로 변경
const newColumn = {
...sourceColumn,
itemIds: newItemIds,
};
const newData = {
...data,
columns: {
...data.columns,
[newColumn.id]: newColumn,
},
};
setData(newData);
//여기까지 기존소스
}else{
//출발지와 도착지가 이제 다르기 때문에 따로 만들어줍니다.
const sourceItemIds = Array.from(sourceColumn.itemIds);
const destinationItemIds = Array.from(destinationColumn.itemIds);
sourceItemIds.splice(source.index, 1);
destinationItemIds.splice(destination.index, 0, draggableId);
const newSourceColumn = {
...sourceColumn,
itemIds: sourceItemIds,
};
const newDestinationColumn = {
...destinationItemIds,
itemIds: destinationItemIds,
};
const newData = {
...data,
columns: {
...data.columns,
[newSourceColumn.id]: newSourceColumn,
[newDestinationColumn.id]: newDestinationColumn,
},
};
setData(newData);
}
};