지금까지 recoil에 대해서 atom, selector에 대해서 학습해 보았다.
이 둘을 적용해서 간단한 분 -> 시로 변경하는 것을 해보려고 한다.

간단하니까 빠르게 코드를 작성해보기로 한다!

코드를 간단하게 살펴보자면,
두개의 input 창이 있다. 각각 minutes, hours를 받는다.
여기서 우리가 해야할 것은 minutes를 입력하면-> hours로 계산되는 것이다.
atoms.tsx -> recoil 코드를 먼저 살펴보자면
import { atom, selector } from 'recoil';
export const minuteState = atom({
key: 'minutes', // 이 atom의 고유 식별자
default: 0, // 초기 상태로 설정된 기본 값
});
export const hourSelector = selector({
key: 'hours', // 이 selector의 고유 식별자
get: ({ get }) => {
const minutes = get(minuteState); // minuteState의 현재 값을 가져온다.
return minutes / 60; // 분을 시간 단위로 변환한다.
},
});
minuteState
minuteState는 atom -> atom은 Recoil의 기본 상태 저장소로서, 애플리케이션에서 공유할 수 있는 상태이다. hourSelector
Hooks
useRecoilState(minuteState)를 사용하여 minuteState의 상태 값을 읽고 업데이트를 한다. useRecoilValue(hourSelector)를 사용하여 hourSelector의 상태 값을 읽는다.onMinutesChange 함수
onMinutesChange 함수는 input 요소의 값이 변경될 때 호출된다.setMinutes(+event.currentTarget.value) 를 통해 입력된 값을 숫자로 변환하여 minuteState를 업데이트한다.컴포넌트 구조
value={minutes}로 현재 분(minute)의 값을 표시하고, onChange={onMinutesChange}로 값이 변경될 때 상태를 업데이트한다. 이전에 recoil의 selector 를 사용했을 때, get()함수를 사용했었다.
get(): atom의 값을 가져와서 selector의 출력값(return)을 계산한다.
set(): atom의 값을 가져와서 다른 atom의 값을 설정하거나 상태를 변경하는 데 사용된다.
set() 함수를 사용해서
이전에는 분 -> 시로 변경했다면, 이제는 시 -> 분 변경도 가능하게 해보자!

selector에서는 set() 함수를 사용할 수 있다.
set() 함수는 selector에서 파생된 상태를 변경하기 위한 메서드이다.
주로 atom의 값을 다른 값으로 변환하거나 계산된 값을 사용하여 상태를 업데이트하는 데 사용한다.
여기서, set()함수를 살펴보자면,
set: ({ set }, newValue) :
첫 번째 인자: 객체로 { set } 를 받는다.
set: 다른 atom이나 selector의 값을 설정할 수 있는 함수이다.
두 번째 인자: newValue,
--> selector에 설정하려는 새로운 값이다.이는 selector가 set될 때 전달되는 값이다.
값 변환: newValue는 사용자가 설정하려는 새로운 시간 값이다. 이 값을 Number(newValue)로 숫자로 변환한 후 * 60을 통해 시간(hour)을 분(minute)으로 변환한다.
minutes를 set(minuteState, minutes) : 를 간단하게 설명하자면,
설정한다! 뭐를 ? minuteState를 minutes로 ! 상태 업데이트를 한다! 라고 이해하면 된다!
const [hours, setHours] = useRecoilState(hourSelector) 를 통해서 hourSelector를 받아왔다. 여기서 hours는 get함수에서 return 한 값을 의미하고, setHour는 set함수를 의미한다.
전체 코드로 살펴보자면,
hours를 받는 input에서 onHoursChange() 이벤트 함수와, value= {hours}로 받는다.
여기서 hours -> hourSelector에서 return minutes / 60 하는 값을 의미한다.
그럼, 이제 onHoursChage 이벤트 함수가 있으니까, input 값이 변경이 된다는 말이다.
onHoursChange 즉, input의 변화가 있을 때, setHours()함수가 실행된다.
setHours(현재 입력한 숫자)가 들어가고, hourSelector에서 set()함수를 호출한다!
set: ({ set }, newValue) => {
const minutes = Number(newValue) * 60;
set(minuteState, minutes);
},
현재 입력한 숫자가 newValue에 들어가서 * 60한 값이 minutes에 저장되고,
minutes가 minuteState의 값을 업데이트 하게 되면서,
시 -> 분으로 변경되는 것이다!
react-beautiful-dnd는 React 기반의 드래그 앤 드롭 인터페이스를 구현할 수 있게 해주는 라이브러리이다.
이 라이브러리는 사용자 경험과 접근성을 고려하여 직관적인 API를 제공합니다. 주로 리스트 정렬이나 보드 같은 애플리케이션에서 항목을 끌어다 놓아 재배치하거나 이동하는 기능을 구현하는 데 사용된다.
핵심 개념과 컴포넌트

<DragDropContext /> : 드래그 앤 드롭을 가능하게 허용할 부분을 감싸주는 태그
onDragEnd : 드래그를 끝낸 시점에 호출되는 함수 (필수 속성)<Droppable /> : 드롭이 가능한 부분을 감싸주는 태그
<Draggable /> : 드래그가 가능한 부분을 감싸주는 태그
그럼 간단하게 기능을 구현 해보자!
--> 두 개의 리스트 항목(One과 Two)을 드래그 앤 드롭을 할 수 있도록 시작!

DragDropContext : onDragEnd 콜백 함수를 필수적으로 전달 -> 드래그가 끝났을 때 호출되어, 상태를 변경하거나 정렬을 처리하는 로직을 담을 수 있다 (지금은 구현하지 않아서, 드래그 후 원래 상태로 돌아오게 된다)
Droppable : Droppable은 함수형 자식을 사용하여 magic 객체를 반환한다(provided 라고도 한다).
--> 내부 JSX 태그에 ref={magic.innerRef} {...magic.droppableProps} 속성을 부여해줘야 한다
Draggable : Draggable은 함수형 자식을 사용하여 magic 객체를 반환한다.
magic.dragHandleProps 를 사용하여 사용자가 드래그를 시작할 수 있는 핸들을 지정할 수 있다.
Draggable 로 두 개의 리스트 항목(li)을 정의한다.
각각 draggableId와 index가 지정되어 있다. 항목들은 magic.innerRef와 magic.draggableProps로 드래그 앤 드롭 속성을 부여받으며, magic.dragHandleProps를 통해 사용자가 항목을 드래그할 수 있는 핸들(span)을 지정한다.
즉, <span {...magic.dragHandleProps}>🎊</span> 인 🎊아이콘 부분만 드래그가 가능해진다.

이모티콘만 cuser도 변경되고, 드래극 가능하다는 것을 확인할 수 있다!
const todos = ['a', 'b', 'c', 'd', 'e', 'f']
우선, 화면에 뿌려보는 것을 우선으로 하기 위해서 간단한 todos 배열을 만들어 보았다.

todos를 어디에 뿌려야 할 까 ? --> 리스트 즉 li 가 어디서 나타나는지를 확인하면 된다.
li--> <Draggable/> 태그 에서 일어나야 한다.
todos를 map으로 todo와 , index를 넘겨주어, <Draggable/> 의 index에 index를 넘겨주면 된다.
Draggable 컴포넌트: Draggable은 각각의 드래그 가능한 항목을 감싸고 있으며, map 함수를 통해 todos 배열의 각 항목을 반복하며 생성된다.
draggableId는 각 항목의 고유 ID로, 드래그 앤 드롭 기능을 구현하는 데 필수적이다.
또한 이들을 styled-component 로 꾸며주면 된다.
const Wrapper = styled.div`
display: flex;
max-width: 480px;
width: 100%;
margin: 0 auto;
justify-content: center;
align-items: center;
height: 100vh;
`;
const Boards = styled.div`
display: grid;
width: 100%;
grid-template-columns: repeat(1, 1fr);
`;
const Board = styled.div`
padding: 20px 10px;
padding-top: 30px;
background-color: ${(props) => props.theme.boardColor};
border-radius: 5px;
min-height: 200px;
`;
const Card = styled.div`
background-color: ${(props) => props.theme.cardColor};
border-radius: 10px;
padding: 5px 10px;
margin-bottom: 5px;
`;
요로코롬 꾸며주면

요렇게 나타나게 된다!
뿅
이제 recoil를 적용시켜보자
export const toDoState = atom({
key: 'toDO',
default: ['a', 'b', 'c', 'd', 'e', 'f'],
});
라고 toDoState 를 생성해주고,
우리는 const [toDos, setTodos] = useRecoilState(toDoState)
useRecoilState()를 사용해 value와 업데이트 할 수 있는 함수를 모두 불러왔다.
이제, 드래그 후 이동한 위치를 고정(?) 시키기 위해서 onDragEnd() 함수를 고쳐야 할 것이다.
현재 드래그 하고 다른 위치로 이동하면, 원래 상태로 돌아간다. 돌아가지 않고 이동한 위치에 위치할 수 있도록 하는게 지금의 목표이다.
먼저,
const onDragEnd = (argus: any) => {
console.log(argus);
};
argus를 출력하면서 onDragEnd에는 어떤 인자를 받는지 확인해 보았다.
f-> a앞으로 즉 인덱스 0번으로 위치를 옮겼을 때, 출력되는 콘솔을 살펴보면,

source의 index -> f의 위치 ,destination의 index -> 이동한 위치를 나타내는 것을 확인할 수 있다.
또한, draggabledId : "f"를 통해서 어떤 것을 이동했는지도 확인할 수 있다.
자 이제, argus : any가 아닌 {destination, source} 를 받아서, 인덱스 5번에 위치해있던 f를 0번에 이동을 시켜주면 된다.
여기서 사용할 메서드는 splice()이다.
const x = ['a', 'b', 'c', 'd', 'e', 'f'] 가 있다고 가정해보자
f 를 a 앞으로 이동시키고 싶을 때,
x.splice(5,1); --> 인덱스 5번부터 1개를 제거한다.
x.splice(0,0,'f'); --> 인덱스 0번에서 0개 제거하고 'f'를 추가한다.
를 하면 const x = ['f','a', 'b', 'c', 'd', 'e'] 가 된다.
--> 이를 활용하면 될 것이다!
기본적으로 splice에서 중요한 점은 -> 한 자리에서 array를 수정하고 변형시킨다는 것이다.
또한 , 기본적으로 array에서 splice를 사용하면 그 array를 수정한다는 것을 의미한다.
이제, splice를 가지고 드래그를 했을 때, 이동이 가능하도록 코드를 수정해보자!

간단하게 실행 과정을 설명하자면,
이제 onDragEnd() 부분에 깊게 생각해보자
이 함수는 드래그 앤 드롭이 완료되었을 때 호출되면서, 인자로 DropResult 타입의 객체 를 받는다.
드롭 위치 없으면 반환 : if (!destination) { return; }는 드롭 위치가 없으면 함수를 종료한다.
상태 업데이트 : setTodos를 사용해 To-Do 리스트를 업데이트한다.
oldTodos는 현재 상태이며, 배열을 상태 --> copyTodos는 oldTodos의 복사본.
splice를 사용해 항목을 이동합니다:
copyTodos.splice(source.index, 1) 에서 드래그한 항목을 원래 위치에서 제거한다 (source.index --> 드래그 한 인자의 index에서부터 1개 삭제)여기서 질문, 그럼 왜 oldTodos를 바로 사용하지 않고 복사해서(copyTodos) 사용하는 이유는 ?
oldTodos를 복사하는 이유는 불변성을 유지하여 상태 관리의 일관성을 보장하고, React의 상태 변경 감지 및 효율적인 렌더링을 지원하기 위해서이다. 불변성을 유지함으로써 React는 상태 변경을 확실히 감지할 수 있고, 이전 상태와 새로운 상태 간의 혼란을 방지할 수 있다.