React의 memo는 성능 최적화 도구 --> 이 도구는 컴포넌트를 메모이제이션하여, 동일한 props로 다시 렌더링될 때 불필요한 재렌더링을 방지
메모이제이션 (Memoization): 결과를 기억해두고, 동일한 입력이 주어졌을 때는 다시 계산하지 않고 기억해둔 결과를 반환하는 방식. memo는 이러한 메모이제이션을 통해 컴포넌트의 불필요한 재렌더링을 막는다.
우리가 작성했던 코드를 DragabbleCard.tsx DragabbleCard 부분을 따로 빼서 깔끔하게 정리한 후,
console.log(toDo, rendering) 콘솔을 찍어보면서 렌더링이 얼마나 되는지 확인해 보았다. 
나는 단순히 f와 e의 자리를 바꾼 것인데, 나머지 a,b,c,d가 모두 다시 리렌더링이 되고 있다.
--> 불필요한 렌더링이 발생하고 있다는 점이다
지금은 간단한 동작만 수행하지만, 복잡한 계산이나 api를 불러온다면, 불필요한 곳까지 리렌더링이 이루어지면 코드가 너무 느려지지 않을까?!
이때, 우리가 React.memo를 통해서 리렌더링을 방지할 수 있다.

React.memo(DragabbleCard) 이렇게 작성을 해두면,
react에게 prop이 변하지 않았다면, DraggbleCard를 다시 렌더링 하지마 ! 라고 이야기 해준다.
또한, prop가 변한 item만 다시 렌더링이 되도록 지시!를 내려준다고 생각하면 될 것 같다
위와 같이 f-> e의 자리만 바꾸었을 때, 바뀐 f와 e만 렌더링이 이루어지고, 나머지 a,b,c,d는 리렌더링이 이루어지지 않는 것을 확인할 수 있다!
이제 보드판 3개를 만들 시간이다
TO_DO, DOING, DONE 의 역할을 할 보드판을 3개를 만들어야 한다
컴포넌트를 각각 만들 수 있겠지만, map()을 사용해서 루프를 돌리면 될 것 같다!

ITodoState
toDoState
to_do: ['a', 'b']
doing: ['c', 'b', 'e']
done: ['f']
그럼 이제 Board.tsx Board 를 따로 컴포넌트를 만들도록 하겠다.
Board는 toDos(배열)과 boardId를 props로 받아야 한다.
boardId
react-beautiful-dnd 라이브러리에서 Droppable 컴포넌트의 droppableId로 사용되어, 어떤 보드에 어떤 할 일을 드롭할 수 있는지 구분한다.toDos
근데 여기서 중요한 점이 있다.
이제 toDoState의 default값이 배열이 아니기 때문에 (객체가 되었다) map()을 사용하지 못한다는 점이다.
그럼 어떻게 해야 할까?

Object.keys(x).map(boardId => x[boardId])
Object.keys(x): 객체 x의 키(a,b) 들을 배열로 반환한다. .map(boardId => x[boardId]) : 반환된 키 배열을 순회하며 각 키에 대해 x 객체에서 해당 값을 가져온다.쉽게 설명하자면,
Object.keys(x) : ['a', 'b']를 반환
--> ['a', 'b'].map(boardId => x[boardId]) :
이걸
to_do: ['a', 'b'],
doing: ['c', 'b', 'e'],
done: ['f'],
에 적용해볼까 !
Object.keys(toDo)를 하면서 toDO 객체에 있는 key들을 모두 불러온 뒤(to_do, doing, done) 이들을 각각 toDo[boardId]를 통해
-> toDo[to_do], toDo[doing], toDo[done] 이런 식으로 할 수 있게 된다!
그럼, toDo에 to_do를 불러오면서 ['a', 'b'] 로 불러올 수 있는 것이다!

이제 코드에 적용을 해보자
<Board/> 에서 받을 수 있는 받아야 하는 props는 toDos, boardId 이기 때문에,
{Object.keys(toDos).map((boardId) => 를 통해서 toDos의 key를 배열로 받아온 뒤, 그것들을 각각을 boardId에 받아온다.
이후, <Board/> 컴포넌트에 boardId, toDos에 넘겨준다.
boardId --> to_do, doing, done과 같이 board의 위치
toDos --> toDos[boardId]
const [toDos, setTodos] = useRecoilState(toDoState); toDoState에서 불러온 toDos의 default 값들을 불러오는 것!
이라고 생각하면 된다.
이렇게 적용시키면,


info를 콘솔에 찍어서 어떤 것들이 필요할 까 살펴보아야 한다.

여기서 필요한 것들은 draggableId, source, destination 이다.
draggableId : 이동한 item이 무엇인지 알기 위해
source : 이동하려고 하는 item의 현재 index, droppableId (어떤 Board에 있는지)
destination : 이동을 한 index, droppableId(어떤 Board로 이동했는지) 이다.
이제 이 정보를 가지고, 코드를 작성해보자.
우선, 하나의 board 안에서만 이동이 가능하게 하기 위해서
if(source.droppableId === destination.droppableId) 를 체크한 뒤, 코드를 실행시켜야 한다.

또한, 변화가 일어난 Board만 복사하고 싶다
고로, allBoards[source.droppableId] 를 통해
source.droppableId는 드래그가 시작된 보드의 ID를 나타낸다.
allBoards[source.droppableId] 는 해당 보드의 할 일 목록을 나타내는 배열을 가져오게 된다.
따라서 const boardCopy = [...allBoards[source.droppableId]] 는 source.droppableId에 해당하는 보드의 할 일 목록 배열을 복사하는 코드가 된다.
이렇게 받아온 boardCopy 의 해당 source.index에서 제거하고, destination.index(드롭한 곳)에 해당 draggableId를 추가한다.
여기서 중요한 점!
toDoState의 default 형태를 유지하기 위해서 변화된 보드와 변화되지 않는 보드를 모두 포함하여 반환해야 한다. 반환되는 객체는 원래의 allBoards 객체와 동일한 구조여야 한다.
--> 따라서 변화된 보드의 할 일 목록을 업데이트하고, 나머지 보드의 할 일 목록은 변경되지 않은 상태로 유지하면 된다.
return 값에 {
...allBoards, // 나머지 변화되지 않는 보드
[source.droppableId]: boardCopy // 변화된 보드의 할 일 목록을 업데이트
}

간단하게 그림을 보면서 어떻게 해야하는지 생각해보자
자, Doing -> c를 Done -> f뒤에 배치하고 싶다.
Doing : ['d','e'],
Done : ['f','c']가 되어야 한다는 의미이다.
여기서 source -> Doing, destination -> Done을 나타내는건 이제 다 알것이다!
(이동을 시작한 Doing이 source, 드롭을 한 Done이 destination)
그럼 soure가 진행된 곳과, destionaion이 진행된 곳 두곳을 복사해야 한다는 이야기이다!
이제 코드로 작성해보자

if (destination.droppableId !== source.droppableId) { : 드래그가 끝난 곳과 드래그가 시작한 곳의 droppableId 즉 board가 다를 때 수행하는 코드임을 알려준다.
const sourceBoard = [...allTodos[source.droppableId]];
const destinationBoard = [...allTodos[destination.droppableId]];
sourceBoard.splice(source.index, 1);
destinationBoard.splice(destination.index, 0, draggableId);
return {
...allTodos,
[source.droppableId]: sourceBoard,
[destination.droppableId]: destinationBoard,
};
이렇게 코드를 작성하게 되면,

이제 다른 Board에도 잘 옮겨지는 것을 확인할 수 있다!
이제 옮겨지는 것까지 완료를 했다. 옮겨질 때, 색상도 같이 변경되면 옮겨지는 효과도 더 줄 수 있을 것 같다.
card가 떠날 때, 새로운 card가 들어올 때, 기본 배경 이런 식으로 3가지 효과를 주면 좋을 것 같다
이때 줄 수 있는 속성은 <Droppable> 컴포넌트의 children로 제공되는 함수들을 살펴보면 된다.
isDraggingOver={info.isDraggingOver}
info.isDraggingOver 는 사용자가 드래그하는 항목이 현재 이 Droppable 컴포넌트 위에 있는지 여부를 나타낸다. isDraggingFromThis={Boolean(info.draggingFromThisWith)}
info.draggingFromThisWith 는 사용자가 드래그하는 항목의 ID를 제공한다.
코드를 이렇게 적용해 보았다.
<Droppable> 컴포넌트는 magic, info의 인자를 넘겨준다.

props.isDraggingOver가 true인 경우 :
드래그 항목이 이 영역 위에 있을 때, 배경색이 pink로 설정
props.isDraggingFromThis가 true인 경우 : 드래그 항목이 이 영역에서 드래그가 시작된 경우, 배경색이 red로 설정
드래그가 이 영역과 관련이 없을 때, 배경색이 blue로 설정 된다는 것을 의미한다.
'ref' --> React에서 DOM 요소 또는 클래스 컴포넌트 인스턴스에 직접 접근하거나 제어할 때 사용하는 특수한 속성을 의미한다.
이제, 직접 ToDo를 입력할 수 있도록 !
<input>와 <button> 태그를 만들어보자
<input placeholder="grab me" />
<button onClick={onClick}>click me</button>
button에는 onClick 이벤트 함수를 주면서, button이 클릭될 때, input창이 focus() 되도록 적용하고 싶다.

inputRef
const inputRef = useRef<HTMLInputElement>(null) :
useRef는 <HTMLInputElement>를 사용하여 참조가 HTML 입력 요소임을 지정한다. <input ref={inputRef} placeholder="grab me" /> : ref를 <input> 요소에 연결하여, inputRef가 해당 DOM 노드(입력 필드)를 참조하게 한다.
const onClick = (event: React.FormEvent<HTMLButtonElement>) => {
inputRef.current?.focus();
};
그런데 Board 컴포넌트에는 한가지 ref가 더 쓰인다

magic.innerRef
magic.innerRef는 react-beautiful-dnd 라이브러리에서 제공하는 ref로, 드롭 가능한 영역을 참조한다....magic.droppableProps 를 Area에 전달하여 드롭 가능한 영역의 속성을 적용한다.inputRef: useRef를 사용하여 생성한 참조를
<input>요소에 연결하여 포커스 설정에 사용.
magic.innerRef: react-beautiful-dnd에서 제공하는 ref로, 드래그 앤 드롭 가능한 영역을 참조하여 라이브러리의 드래그 앤 드롭 동작을 구현.

이제 atom의 toDoState의 default 값을 객체 안에
즉 , {To DO : [], Doing : [], Done : []} 형태로 수정해 주었다.
이전에는 내가 기본값을 주었다면, 이제는 입력을 받아야 하기 때문에, 빈 배열로 주었으며,
ITodo interface를 제공해주어, ITodo가 어떤 타입인지 지정해 주었으며, toDoState의 타입을 지정하는 ITodoState의 배열의 형태를 같이 이정해 주었따.

이제 input 박스를 생성하기 위해서 --> form을 생성해야 한다.
--> react-hook-form을 사용해보았다.
const onDragEnd = (info: DropResult) => {
const { draggableId, destination, source } = info;
console.log(info);
if (!destination) {
return;
}
if (source.droppableId === destination?.droppableId) {
setTodos((allBoards) => {
const boardCopy = [...allBoards[source.droppableId]];
const taskObj = boardCopy[source.index];
boardCopy.splice(source.index, 1);
boardCopy.splice(destination.index, 0, taskObj);
return {
...allBoards,
[source.droppableId]: boardCopy,
};
});
}
if (destination.droppableId !== source.droppableId) {
setTodos((allTodos) => {
const sourceBoard = [...allTodos[source.droppableId]];
const taskObj = sourceBoard[source.index];
const destinationBoard = [...allTodos[destination.droppableId]];
sourceBoard.splice(source.index, 1);
destinationBoard.splice(destination.index, 0, taskObj);
return {
...allTodos,
[source.droppableId]: sourceBoard,
[destination.droppableId]: destinationBoard,
};
});
}
};
같은 보드 내에서의 작업 이동 처리
다른 보드 간의 작업 이동 처리
이후, 원래는 string으로 받아왔던 것들을 --> object로 받아오는 코드로 수정하면 된다!

그럼 이렇게 각각의 항목에서 투두 입력도 잘 되고, 이동도 잘 되는 모습을 볼 수 있다!