TRELLO CLONE(3) - Drag and Drop 구체화

초연2e·2022년 10월 8일
0

MUTSA_Front.archive

목록 보기
9/16

현재는 React JS에서 컴포넌트의 State가 변하면 해당 컴포넌트의 모든 children이 리렌더링된다.
그래서 드래그할때 가끔 떨리는 현상이 발생하는 것이다.

그렇다면 왜 이런 현상이 발생하고 있을까?
이건 리액트의 기본 성질이라고 보면 된다.

부모컴포넌트의 state가 변화하면, 자식컴포넌트의 state도 리렌더링 되는 성질!

그러나 우리는 현재 이 성질이 필요가 없다,,,
이렇게 가끔 어떤 것들은 다시 렌더링되지 않아야하는 경우가 있다.
이 문제를 해결해보자!




React Memo

React memo는 리액트에게 제발 이 component는 렌더링 하지 말라고 말하는 역할을 해준다. 단, prop이 바뀌지 않는다면!

export default React.memo(DragabbleCard);

DraggableCard.tsx의 export 부분을 이렇게 바꿔주자!
react에게 prop이 변하지 않았으면 DraggableCard를 다시 렌더링하지 말라고 말한 것이다.


이렇게 코드를 바꿔주고 실행해보면 드래그해서 순서를 바꾼 component들만 다시 렌더링된다.

처음에 앱을 실행하면 이렇게 되겠지?
a부터 f까지 모두 한번씩 렌더링되는 모습이다.

c를 드래그해서 b와 순서를 바꿔보았다.
보다시피 c와 b만 리렌더링된다!




Multi Board

보드를 여러개로 만들어보자!
일단 state를 object로 바꿔보자
이 object는 각각의 보드가 가질 id를 포함한다.

import {atom} from "recoil";

interface IToDoState {
  [key: string]: string[];
}

export const toDoState = atom<IToDoState>({
  key: "toDo", 
  default: {
      to_do: ["a", "b"],
      doing: ["c", "d", "e"],
      done: ["f"],
    },
});

atom.tsx를 이렇게 만들어주자.
우리는 to_do, doing, done 이 각 기능을 수행하는 보드를 세개 만들어 줄 것이다.
지금은 일단 각 보드들에 값을 직접 넣어줬지만 나중에는 서로 옮겨지게끔도 만들어 줄 것임

이렇게 해주면 App에서 오류가 난다.
우리는 map을 사용했는데 map은 object가 아닌 array에서만 사용할 수 있기 때문..

그래서 Components 폴더에 Board.tsx 파일을 만들어준다. 원래 우리가 board를 만들었던거를 잘라내서 옮겨준다.~ (생겨나는 오류들은 import해주고 props 넘겨주고 하면서 없애자.)

이렇게 해주면 우리는 이 Board 컴포넌트를 재사용할 수 있다.

아까 말했듯이 우리는 map을 사용하지 못하니까 object를 loop할 수 있는 방법을 찾아야한다.

이것을 위해 Object.keys를 사용할 것이다.




Object.keys


Object.keys는 object가 가진 key만 array로 뽑아내주는 역할을 한다.

이걸 이용하면 수동으로 하나하나 출력하는 것이 아니라 자동으로 가져올 수 있다.

  <Boards>
    {Object.keys(toDos).map((boardId) => (
      <Board boardId={boardId} key={boardId} toDos={toDos[boardId]} />
  ))}
    </Boards>

App에 가서 저 부분을 저렇게 고쳐주면
우리는 toDos의 key를 array로 받아와서 boardId를 mapping할 수 있다.

실행해보면 이제 우리는 3개의 보드가 잘 만들어졌다는 것을 알 수 있다.

이렇게!
그런데 문제가.. 이러면 이제 재배열하는 능력을 잃게된다.




같은 보드 내에서 재배열


같은 보드 안에서 재배열이 가능하게 다시 만들어주자..

이것을 위해서는 우리가 drop할 때마다 어떤 정보를 얻었는지를 알아야한다.

일단 우리가 drop할 때마다 onDragEnd라는 함수가 실행된다.

일단 콘솔에 찍어서 확인해보면 우리가 전에 봤던 것과 동일하게 drop할 때마다 방금 옮겨진 요소의 ID, 어떤 board의 어떤 위치에서 이동이 시작되었는지, destination 등을 알 수 있다!

이제 이 onDragEnd를 수정해주자. 이름은 info라고 했다.
info로부터 destination, draggableId, source를 받아온다.

그 후, 일단 source board가 destination의 board와 같은지를 먼저 체크해야한다. 왜냐면 지금 우리가 하는건 같은 보드 내 재배열기능이니까!

조건문을 통해 이를 확인해주자.

그 후는 이제 전에 했던것과 비슷하다.

  1. 수정이 일어난 보드만 복사
  2. 복사본을 기존의 것들 옆에 붙여넣기

일단 복사는.. setToDos에서 진행한다.

  setToDos((allBoards) => {
    const boardCopy = [...allBoards[source.droppableId]];
    boardCopy.splice(source.index, 1);
    boardCopy.splice(destination?.index, 0, draggableId);

이렇게 해주면
allBoards로 들어가서 source의 droppableId로부터 array를 복사한다.
그 후, 이 복사본에서 원하는 부분을 지웠다가, destination에 넣어주면 된다. (전에 했던거랑 똑같음!)

이제 그 setToDos에서

  return {
    ...allBoards,
    [source.droppableId]: boardCopy,
  };

이렇게 return해주면!
다른 모든 board들을 return하면서 [source.droppableId]가 board의 복사본임을 말해주는 것이다.

총정리해보면✍️

function App(){
  const [toDos, setToDos] = useRecoilState(toDoState);
  const onDragEnd = (info: DropResult) => {
    console.log(info);
    const { destination, draggableId, source } = info;
    if (destination?.droppableId === source.droppableId) {
      // same board movement.
      setToDos((allBoards) => {
        const boardCopy = [...allBoards[source.droppableId]];
        boardCopy.splice(source.index, 1);
        boardCopy.splice(destination?.index, 0, draggableId);
        return {
          ...allBoards,
          [source.droppableId]: boardCopy,
        };
      });
    }
  };

이렇게 해주면!!!!!!!!!!!
같은 보드 내에서의 재배열이 가능해진다.




다른 보드로의 재배열


위에서 했던 것과 같은 로직으로 진행된다.

이건 다른 보드로의 재배열이니까 위에서 했던것과 반대로 sourcedroppableIddestinationdroppableId가 다르면 진행된다.


예를 들어 우리가 b를 done으로 옮긴다고 하자.
그러면 일단 source를 복사해와서 복사본에서 b를 지워야한다.
그 후 destination을 복사해서 b를 붙여넣어야한다.
그러면 우리는 source array와 destination array, 총 2개를 복사하게되겠지?
당연히 나머지 다른 board들도 모두 return해주어야한다.

위에서 했던 것과 같은 방식임!

위에서 했던 것과 같이 setToDos 함수를 만들어주어야함.
setToDos는 toDos atom의 modifier함수라는 것을 잊지말자⭐

  if (destination.droppableId !== source.droppableId) {
    // cross board movement
    setToDos((allBoards) => {
      const sourceBoard = [...allBoards[source.droppableId]];
      const destinationBoard = [...allBoards[destination.droppableId]];
      sourceBoard.splice(source.index, 1);
      destinationBoard.splice(destination?.index, 0, draggableId);
		return {
			 ...allBoards,
             [source.droppableId]: sourceBoard,
             [destination.droppableId]: destinationBoard,
    		   };
    });
  }

위에서 했던 것과 같이 코드를 짜준것이다.
좀 길어지고 복잡해보이는데 천천히 읽어보면 위에서 했던 것과 같은 코드라는 것을 알 수 있을 것이다!!!

이걸 실행해보면,, 서로 다른 보드끼리의 재배열도 잘 이루어진다.




Droppable Snapshot

현재는 드래그해서 움직일 때마다 맨 위까지 가야지만 이동이 끝난다.
이 점이 많이 불편해서 이걸 수정해보자.

Droppable Area 넓히기

현재, droppable area를 파란색으로 보이게 style설정해주었다.
이제 부모컴포넌트를 채워 이 파란색 droppable area가 맨 아래까지 이어져보이도록 만들어주고싶다.

Area의 부모컴포넌트인 Wrapper의 styled-components에 가서 displayflex로, flex-directioncolumn으로 해주자.
이렇게 하고 새로고침해도 변하는 건 없다.

그런데 이제 자식컴포넌트인 Area의 styled-components로 가서 flex-grow에 1을 주고 새로고침을 해보장

이러면,, droppable area가 전체영역으로 바뀐다.
이제 보드의 아래쪽에 드롭해도 알아서 위로 올라가서 적용되어 드래그앤드롭이 매우 편해진다!




Drag and Drop 시 board 색상변화 주기 (snapshot)


이제 내가 드래그한게 board에 도착하는지 떠나는지에 따라 area의 색상이 변경되도록 만들어보자🤓

snapshot이라는 argument가 필요하다!!!!!!!!
snapshot을 이용하면 아래와 같은 정보를 얻을 수 있다.

isDraggingOver: boolean
현재 선택한 Draggable이 특정 Droppable위에 드래깅 되고 있는지 여부 확인

draggingOverWith: ?DraggableId
Droppable 위로 드래그하는 Draggable ID

draggingFromThisWith: ?DraggableId
현재 Droppable에서 벗어난 드래깅되고 있는 Draggable ID

isUsingPlaceholder: boolean
placeholder가 사용되고 있는지 여부

등등...

이제 Area 컴포넌트로 가서 isDraggingOver, isDraggingFromThis를 prop으로 전달해줄 것이다.
Area의 styled-components에게 이것들을 prop으로 받을거라고 말해줘야한다.


const Area = styled.div<IAreaProps>`
    background-color: ${(props) =>
        props.isDraggingOver ? "pink" : props.isDraggingFromThis ? "red" : "blue"};
    flex-grow: 1;
    transition: background-color 0.3s ease-in-out;
`;

그냥 쓰면 좀 길어지니까 IAreaProps라는 interface를 만들었다.
이렇게 해주면 보드의 색깔로 드래그해서 무언가를 가져오는 중에는 핑크색을, 드래그해서 떠날 때면 빨간색을, 평소(그냥 가만히 있는 보드)에는 파란색을 보여준다.

transition은 추가로 넣어줬당.




Draggable Card 꾸미기

DraggableCard에서도 droppable에서 했던 것처럼 똑같이 정보들을 가져오고 snapshot을 이용하고 다 할 수 있다.

Draggablestate snapshot

isDragging: boolean
: Draggable이 활발하게 드래그 중이거나 드롭 애니메이션인 경우 true로 설정.

isDragging일 경우 dragging의 색상을 바꿔보자.
일단 아까처럼 Card컴포넌트에 prop으로
isDragging={snapshot.isDragging} 추가해주자.

아까와 동일하게 card는 styled-components니까 isDragging이라는 prop은 인식할 수 없다.
그래서 styled-components로 올라가서 말해주어야한다.

const Card = styled.div<{ isDragging: boolean }>`
  border-radius: 5px;
  margin-bottom: 5px;
  padding: 10px;
  background-color: ${(props) =>
    props.isDragging ? "#e4f2ff" : props.theme.cardColor};
  box-shadow: ${(props) =>
    props.isDragging ? "0px 2px 5px rgba(0, 0, 0, 0.05)" : "none"};
`;

이제 dragging할때의 배경색은 #e4f2ff 가 될 것이고,
다른 때에는 cardColor가 된다.
isDragging일 때 box shadow도 주었기 때문에 드래그하는동안 그림자도 적용될 것임 👍👍👍👍👏👏




이제 우리는 여러개의 board를 관리할 수 있다.

profile
프론트로 멋쟁이되기 대장정,,

0개의 댓글