프로젝트를 진행하며 드래그앤드롭으로 ITEM들을 이동을 드래그앤드랍으로 구현하기로 하고
두가지를 고려하였고, 현재 진행하고 있는 프로젝트의 멘토님에게 드래그앤드롭 성능면에서 어느 선택을 하면 좋을지 질문을 드렸다.
멘토님은 많은 라이브러리가 성능 면에서 고려되어 만들어졌기에 라이브러리를 사용하는것이 좋을 것이다 라고 하셨고 현업에서도 라이브러리를 많이 사용한다고 하셨다.
드래그앤드롭을 구현할 수 있는 라이브러리가 많다.
그 중에서 react-beautiful-dnd를 사용해서 드래그앤드롭을 구현하자
Jira와 Trello를 만든 atlassian 회사에서 만든 오픈 소스 라이브러리
npm install react-beautiful-dnd --save
DragDropContext
dnd를 사용하고자 하는 어플리케이션 영역을 감싸는 Wrapper
Droppable
dnd에서 Drop을 할 수 있는 영역, Draggalbe을 감싸는 Wrapper
Draggable
dnd의 주체가 되는, Drag가 가능한 컴포넌트를 감싸는 Wrapper
onDragStart
: Drag가 시작될때 호출
onDragUpdate
: Drag 진행중일때, 새로운변화가 있을때
onDragEnd
: Drag가 끝났을때
function Board({ boardId }: any) {
const [boards, setBoards] = useRecoilState(taskAtom); // useRecoilState를 사용해서 boards를 가져온다.
const [selectedBoard] = JSON.parse(JSON.stringify(boards)).filter((board: any) => board.boardId === boardId); // JSON.parse(JSON.stringify()) -> 깊은 복사를 위해 사용
let tempBoards = JSON.parse(JSON.stringify(boards)); // 원하는 값을 변경 한 뒤에 tempBoards에 넣어줄 것이다.
let tempList = JSON.parse(JSON.stringify(selectedBoard.list)); // 원하는 값을 변경하기 위함
const onDragEnd = (result: any) => {
if (!result) return;
const [reorderedItem] = tempList.splice(result.source.index, 1); // 내가 드래그 하려고 하는 요소를 tempList에서 제외한다.
tempList.splice(result.destination.index, 0, reorderedItem); // 내가 드래그해서 가려고 하는 목적지에 reorderedItem을 넣어준다.
selectedBoard.list = tempList; // 현재 보드의 list를 새로운 list로 변경한다.
tempBoards = tempBoards.map((board: any) => (board.boardId === boardId ? selectedBoard : board)); // 보드배열을 반복하면서 내가 변경한 보드 ID와 배열안에 있는 보드ID 가 일치하면 변경
setBoards(tempBoards); // taskAtom recoil 업데이트!
};
// 반복을 통해서 리스트들을 보여준다.
return (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="list" direction="horizontal">
{(provided) => (
<S.ListContainer {...provided.droppableProps} ref={provided.innerRef}>
{tempList.map((list: any, index: any) => (
<Draggable draggableId={String(index)} index={index} key={String(index)}>
{(provided) => (
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<div>{list.listTitle}</div>
<List list={list} boardId={boardId} listIndex={index} listId={list.listId} />
</div>
)}
</Draggable>
))}
{provided.placeholder}
</S.ListContainer>
)}
</Droppable>
</DragDropContext>
);
}
export default Board;
function List({ list, boardId, listIndex, listId }: any) {
const [boards, setBoards] = useRecoilState(taskAtom); // recoil에서 관리하는 board들을 담고 있는 데이터
let tempBoards = JSON.parse(JSON.stringify(boards)); //보드들 정보를 tempBoards에 저장
let [selectedBoard] = JSON.parse(JSON.stringify(boards.filter((board) => board.boardId === boardId))); // 선택한 보드 1개를 저장
let tempList = JSON.parse(JSON.stringify(list)); // 선택한 리스트 1개를 저장
let tempLists = selectedBoard.list; // 선택된 보드 1개에서 list 배열을 tempLists에 담는다
let tempCards = JSON.parse(JSON.stringify(list.card)); // 선택한 리스트에서 카드 배열을 tempCards에 담는다
const onDragEnd = (result: any) => {
console.log(`선택한 BoardId : ${boardId}, listId:${listId}`);
if (!result) return;
let [reorderedItem] = tempCards.splice(result.source.index, 1); // 선택한 카드를 배열에서 빼서 reorderedItem에 담는다.
tempCards.splice(result.destination.index, 0, reorderedItem); // 목적지에 reorderedItem을 넣는다.
tempList.card = tempCards; // tempList 객체서 card 배열을 변경한다.
let newLists = tempLists.map((list: any) => (list.listId === listId ? tempList : list)); //리스트 배열 중에서 listId가 일치하는 곳에 새로운 List를 넣어준다.
selectedBoard.list = newLists; // 새로운리스트들을 선택한 보드의 리스트 배열에 넣어준다.
let newBoards = tempBoards.map((board: any) => (board.boardId === boardId ? selectedBoard : board)); // 전체 보드에서 바뀐 보드를 업데이트 해준다.
setBoards(newBoards); //recoil을 재설정한다.
};
// 반복을 통해서 리스트 들을 보여준다.
return (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="Card">
{(provided) => (
<div ref={provided.innerRef} {...provided.droppableProps}>
<S.ListWrapper>
{tempCards.map((card: any, index: any) => (
<Draggable draggableId={String(index)} index={index} key={index}>
{(provided) => (
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
<S.CardWrapper>{card.cardTitle}</S.CardWrapper>
</div>
)}
</Draggable>
))}
</S.ListWrapper>
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
);
}
드래그앤드랍 가능 영역
드래그앤드랍 불가능 영역
이러한 문제점이 있었고 코드를 새로 작성하며 해결했다.
구조는 보드 -> 리스트 -> 카드 순으로 구성된다.
DragDropContext, Droppable, Draggable 범위를 아래와 같이 설정했다.
// Board.tsx
...
return (
<>
<DragDropContext onBeforeDragStart={onBeforeDragStart} onDragStart={onDragStart} onDragEnd={onDragEnd}>
<Droppable droppableId="board" type="moveList" direction="horizontal">
{(provided) => (
<S.BoardContainer ref={provided.innerRef} {...provided.droppableProps}>
{lists.map((list, index) => (
<List key={list.listId} boardId={boardId} listId={list.listId} listData={list} index={index}></List>
))}
<AddList></AddList>
{provided.placeholder}
</S.BoardContainer>
)}
</Droppable>
</DragDropContext>
</>
);
// List.tsx
...
return (
<Draggable draggableId={listId} index={index}>
{(provided) => (
<S.ListWrapper ref={provided.innerRef} {...provided.draggableProps}>
<S.ListContent>
<ListTitle
dragHandleProps={provided.dragHandleProps}
boardId={boardId}
listId={listId}
title={listData.listTitle}
></ListTitle>
<Droppable droppableId={listId} type="moveCard">
{(droppableProvided, droppableSnapshot) => (
<S.ListDroppable ref={droppableProvided.innerRef}>
{listData.cards.map((card: CardData, index: number) => (
<Card
key={card.cardId}
cardId={card.cardId}
listId={listId}
boardId={boardId}
cardData={card}
index={index}
></Card>
))}
<AddCard listId={listId} />
{droppableProvided.placeholder}
</S.ListDroppable>
)}
</Droppable>
</S.ListContent>
</S.ListWrapper>
)}
</Draggable>
);
// Card.tsx
...
return (
<Draggable draggableId={cardId} index={index}>
{(draggableProvided, draggableSnapshot) => (
<S.CardDraggable
ref={draggableProvided.innerRef}
{...draggableProvided.draggableProps}
{...draggableProvided.dragHandleProps}
>
<S.TextAreaWrapper onClick={handleSaveModalData}>
<S.CardHeaderWrapper>
<S.CardTitleWrapper>{cardData.cardTitle}</S.CardTitleWrapper>
<S.DeleteWrapper onClick={handleDeleteCard}>
<CgClose />
</S.DeleteWrapper>
</S.CardHeaderWrapper>
<S.InformationWrapper>
<FcClock size="20" />
</S.InformationWrapper>
</S.TextAreaWrapper>
</S.CardDraggable>
)}
</Draggable>
);
최종 코드에는 DragDropContext, Droppable, Draggable 범위만 나와있고 드래그앤드랍이 발생했을때 어떻게 처리할지에 대해서는 나와있지 않다. 아직 개발중에 있고 완벽하게 마무리되면 수정 예정이다.
안녕하세요~ 포스트 잘 보았습니다. 얘기 한번 나눠볼 수 있을까요?