React JS 마스터클래스(TRELLO CLONE 1 )

짜스의 하루 ·2024년 6월 11일

recoil에 대해서 마저 학습하기

지금까지 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의 기본 상태 저장소로서, 애플리케이션에서 공유할 수 있는 상태이다.
  • minuteState의 키는 'minutes'이며, 기본 값은 0이다. 이 atom은 현재 입력된 분(minute)의 값을 저장한다

hourSelector

  • hourSelector는 selector -> selector는 atom에서 파생된 상태를 계산하는 데 사용된다.
  • hourSelector의 키는 'hours'이고, get 함수는 minuteState의 현재 값을 가져와서 minutes / 60의 결과를 반환한다
    --> 즉, 분(minute)을 시간(hour)으로 변환하여 제공하게 된다.

Hooks

  • useRecoilState(minuteState)를 사용하여 minuteState의 상태 값을 읽고 업데이트를 한다.
  • minutes는 현재 minuteState의 값을 나타내고, setMinutes는 이를 변경하는 함수이다.
  • useRecoilValue(hourSelector)를 사용하여 hourSelector의 상태 값을 읽는다.
  • hours는 minuteState에서 파생된 값을 나타낸다. --> 즉, 입력된 분(minute)을 시간(hour) 단위로 변환한 값이다.

onMinutesChange 함수

  • onMinutesChange 함수는 input 요소의 값이 변경될 때 호출된다.
  • setMinutes(+event.currentTarget.value) 를 통해 입력된 값을 숫자로 변환하여 minuteState를 업데이트한다.

컴포넌트 구조

  • 첫 번째 input은 사용자가 분(minute)을 입력할 수 있도록 한다.
  • value={minutes}로 현재 분(minute)의 값을 표시하고, onChange={onMinutesChange}로 값이 변경될 때 상태를 업데이트한다.
  • 두 번째 input은 시간을 시간(hour) 단위로 표시한다.
  • value={hours} 로 현재 시간(hour)을 표시하고, readOnly 속성으로 읽기 전용으로 설정되어 있음
    --> onChangeEvent가 없고, value만 있을 경우, 읽기 전용
    이는 사용자가 직접 수정하지 못하도록 하여, 자동 계산된 시간을 표시하는 역할을 한다.

selector --> set()

이전에 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-beautiful-dnd는 React 기반의 드래그 앤 드롭 인터페이스를 구현할 수 있게 해주는 라이브러리이다.
이 라이브러리는 사용자 경험과 접근성을 고려하여 직관적인 API를 제공합니다. 주로 리스트 정렬이나 보드 같은 애플리케이션에서 항목을 끌어다 놓아 재배치하거나 이동하는 기능을 구현하는 데 사용된다.

핵심 개념과 컴포넌트

<DragDropContext /> : 드래그 앤 드롭을 가능하게 허용할 부분을 감싸주는 태그

  • onDragEnd : 드래그를 끝낸 시점에 호출되는 함수 (필수 속성)

<Droppable /> : 드롭이 가능한 부분을 감싸주는 태그

  • droppableId : 구분자 (필수 속성)
  • children은 반드시 함수여야 함 (JSX 태그X) -> 따라서 함수로 JSX 태그를 리턴하는 형식을 자식에 넣어주어야 한다

<Draggable /> : 드래그가 가능한 부분을 감싸주는 태그

  • draggableId : 구분자 (필수 속성)
  • index : 정렬을 위한 데이터 (number)
    children은 반드시 함수여야 함 (JSX 태그X) -> 따라서 함수로 JSX 태그를 리턴하는 형식을 자식에 넣어주어야 한다

그럼 간단하게 기능을 구현 해보자!
--> 두 개의 리스트 항목(One과 Two)을 드래그 앤 드롭을 할 수 있도록 시작!

DragDropContext : onDragEnd 콜백 함수를 필수적으로 전달 -> 드래그가 끝났을 때 호출되어, 상태를 변경하거나 정렬을 처리하는 로직을 담을 수 있다 (지금은 구현하지 않아서, 드래그 후 원래 상태로 돌아오게 된다)

Droppable : Droppable은 함수형 자식을 사용하여 magic 객체를 반환한다(provided 라고도 한다).
--> 내부 JSX 태그에 ref={magic.innerRef} {...magic.droppableProps} 속성을 부여해줘야 한다

Draggable : Draggable은 함수형 자식을 사용하여 magic 객체를 반환한다.
magic.dragHandleProps 를 사용하여 사용자가 드래그를 시작할 수 있는 핸들을 지정할 수 있다.

Draggable 로 두 개의 리스트 항목(li)을 정의한다.
각각 draggableIdindex가 지정되어 있다. 항목들은 magic.innerRefmagic.draggableProps로 드래그 앤 드롭 속성을 부여받으며, magic.dragHandleProps를 통해 사용자가 항목을 드래그할 수 있는 핸들(span)을 지정한다.

즉, <span {...magic.dragHandleProps}>🎊</span> 인 🎊아이콘 부분만 드래그가 가능해진다.

이모티콘만 cuser도 변경되고, 드래극 가능하다는 것을 확인할 수 있다!


'react-beautiful-dnd' 로 간단한 리스트 만들어보기

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를 가지고 드래그를 했을 때, 이동이 가능하도록 코드를 수정해보자!

간단하게 실행 과정을 설명하자면,

  • 드래그 시작: 사용자가 To-Do 항목을 드래그한다.
  • 드래그 종료: 사용자가 항목을 다른 위치에 드롭한다.
  • onDragEnd 호출: 드래그 앤 드롭이 끝나면 onDragEnd가 호출된다.
  • 상태 업데이트: onDragEnd 함수setTodos를 사용해 To-Do 리스트 상태를 업데이트한다. 드래그된 항목을 제거하고 새 위치에 추가한다.

이제 onDragEnd() 부분에 깊게 생각해보자

이 함수는 드래그 앤 드롭이 완료되었을 때 호출되면서, 인자로 DropResult 타입의 객체 를 받는다.

  • draggableId: 드래그한 항목의 ID.
  • destination: 드롭한 위치의 정보 (없을 수도 있음).
  • source: 드래그를 시작한 위치의 정보.

드롭 위치 없으면 반환 : if (!destination) { return; }는 드롭 위치가 없으면 함수를 종료한다.
상태 업데이트 : setTodos를 사용해 To-Do 리스트를 업데이트한다.

oldTodos는 현재 상태이며, 배열을 상태 --> copyTodos는 oldTodos의 복사본.

splice를 사용해 항목을 이동합니다:

  • 삭제 : copyTodos.splice(source.index, 1) 에서 드래그한 항목을 원래 위치에서 제거한다 (source.index --> 드래그 한 인자의 index에서부터 1개 삭제)
  • 추가: copyTodos.splice(destination.index, 0, draggableId);에서 드래그한 항목을 새 위치에 추가한다 (destination.index -> 드롭한 위치에서 0개 삭제 후 draggabledId 추가)

여기서 질문, 그럼 왜 oldTodos를 바로 사용하지 않고 복사해서(copyTodos) 사용하는 이유는 ?
oldTodos를 복사하는 이유는 불변성을 유지하여 상태 관리의 일관성을 보장하고, React의 상태 변경 감지 및 효율적인 렌더링을 지원하기 위해서이다. 불변성을 유지함으로써 React는 상태 변경을 확실히 감지할 수 있고, 이전 상태와 새로운 상태 간의 혼란을 방지할 수 있다.

profile
2024. 01. 02 ~ 백앤드 공부 시작, 2024. 04.01 ~ 프론트 공부 시작

0개의 댓글