노마드코더 ReactJS 마스터클래스 6

딩쓰·2023년 4월 24일
0
post-thumbnail

#6.11 ~ 6.14 2023.04.24 (월)

6.11 Add To Do

이번 강의부터는 recoil을 중점으로 해보자. toDo를 제출하면 ul로 렌더링되게 해보자.
ToDoList.tsx파일을 src/component 폴더안에 옮겨주고, 당장은 ToDoList 컴포넌트에 모든 걸 넣지만 나중에 다분리시겨줄 거임.

  • toDoState라는 atom을 만들어주고, value값과 수정함수를 불러오기 위해useRecoilState를 만들어 인자로 넣어줌.
  • toDos value는 타입스크립트에게는 항상 빈 배열이어야 하기때문에 수정함수인 setToDos의 동작이 허용되지 않음.
    -> interface를 만들어 타입스크립트에게 toDo가 어떻게 생긴지를 알려주자!
  • useRecoilState(atom): value와 수정함수 둘다 얻을때 쓰는 함수
  • useRecoilValue(atom): atom 값을 불러오기 위해 쓰는 함수
  • useSetRecoilState(atom): atom 값을 변경하기 위해 쓰는 함수
interface IToDo {
  text: string;
  id: number;
  category: "TO_DO" | "DOING" | "DONE";
}

const toDoState = atom<IToDo[]>({
  key: "toDo",
  default: [],
});
  • 타입스크립트에서는 원하는 갯수의 옵션으로 제한하는 것이 가능함.
    -> 카테고리를 to do, doing, done 세가지로 지정해줌
    -> 이제 To Do를 만들면, 명시된 3개 중 하나의 string을 가져야 함.
  • atom에게 type이 ToDo의 배열임을 알려줌.
  • 타입스크립트는 이제 toDos가 IToDo 객체로 이뤄진 배열임을 알게됨.

폼이 제출되고 데이터가 모두 유효하다면 state를 바꾸는것을 해보자!

setToDos([])
setTodos(() => {})
toDos.push() // X
  • 수정함수는 두 개의 동작을 할 수 있는데, state를 직접적으로 설정해 줄 수 있거나 다른 함수를 받을 수도 있음.
  • 함수를 받는다면, 함수의 리턴값이 새로운 state가 될거임.
  • 기본적으로 새로운 state를 return 해야함. toDos에 바로 push할 수 없음. (기존의 toDos를 mutate 하기 때문에)

  • setToDos()에 이전의 state를 oldToDos로 받으면 oldToDos는 배열이기 때문에 구조분해할당으로 []를 벗겨줌.
  • 기존의 data: IForm{toDo}: IForm로 바꿈.
    -> data는 input창에 입력한 값.
    -> 괄호를 열어 toDo만을 가져오게 함.
  • { text: toDo, id: Date.now(), category: "TO_DO" }로 interface 정한 값이랑 똑같이 들어오게 해줌.

6.12 Refactoring

이제 할 일을 완료할 수 있는 기능을 추가하기위해 "TO_DO"를 클릭하면 "DOING"이나 "DONE"으로 또는 반대로도 바뀔 수 있게 만들거임.

시작하기전 ToDoList.tsx에 있던 코드들을 분리시키는 리팩토링을 해보자.

  • CreateToDo.tsxToDo.tsx 파일을 만들어주고 form을 CreateToDo.tsx로 옮겨줌 (li는 나중에 옮겨줄거임.)
    💡 무작정 모든 파일들을 만드는 것보다 코드가 복잡해도 일단 작동을 하게 만들고 그 후에 코드를 정리하는게 좋음.

  • 위와 같이 옮기면 타입스크립트가 에러를 말해줌. 이 부분을 하나씩 고치면 됨.

  • CreateToDo.tsx의 에러를 하나씩 고쳐줌.
  • 위와 같은 에러가 나는 이뉴는 handleValid는 toDo의 타입을 필수적으로 받지만 useForm에는 타입이 지정되 있지 않아서임
    -> useForm<IForm>(); 으로 고쳐주자.

  • ToDoList.tsx에서 안쓰는 코드를 지워줌.

  • 컴포넌트들간에 prop을 내려주지 않아도 toDo가 화면에 잘 렌더링됨.
    -> 이제 모든 컴포넌트들이 서로를 의존하지 않고, atom으로만 연결됨!

  • ToDoList.tsx의 li를 ToDo.tsx로 옮겨주자.

  • ToDo.tsx의 li는 text만 필요하니 prop으로 받아오고 IToDo 인터페이스로 타입을 설정해줌.

  • ToDoList.tsx는 map함수안에서 ToDo컴포넌트를 렌더링 되게 함
    -> 여기서 ToDo의 에러는 ToDo컴포넌트에 text,id,category prop이 필요하다고 타입스크립트가 내고 있음.

  • 첫번째와 같이 prop을 내려주는 방법도 있지만 <ToDo {...toDo} />로도 간단하게 내릴 수 있음.
    -> 이렇게 써도 작동하는 이유는, toDos 배열의 toDo원소 하나하나가 ToDo 컴포넌트에 필요한 props와 같은 모양이기 때문!

  • ToDo 컴포넌트에 카테고리 버튼을 3개 만들어주고, ToDoList 컴포넌트에는 key prop을 추가해 줌.

6.13 상황에 맞게 카테고리버튼 렌더링하기

사용자들이 버튼을 이용해서 toDo의 카테고리를 바꿀 수 있게 하는 기능과 toDo의 카테고리에 따라서 알맞은 버튼만 보이게 해보자.


위와 같이 카테고리가 "TO_DO"이 아닐 때만, To Do버튼이 보이게 만듦.
-> 현재 카테고리(To Do)가 아닌 다른 카테고리 2개(Doing,Done)가 보이게 됨.

이제 toDo의 카테고리를 바꾸는 함수를 만들어 보자.
사용자가 Doing 버튼을 클릭하면, 인자를 통해서 Doing 버튼이 클릭됐다는걸 알아야 함! (그래야 toDo의 카테고리를 "DOING"으로 바꿀 수 있음)

  • 버튼을 클릭하면 인자를 넘기기 위해서 새 익명 함수(() => )를 선언 해서 onClick 함수를 호출하게 만듦.
  • <button onCkick={onClick}/> : 이렇게 해도 작동은 하지만, 인자는 넘겨지지 않음

  • newCategory의 타입을 인터페이스로 정해주고 함수가 잘 작동되는지 console로 확인함.

위에서 했던 새 익명함수를 선언하는 방법말고 다른 방법도 있음

  • 위와 같이 onClick이벤트에 onClick함수만 넣어주고 button에 name 속성을 만들어서 카테고리를 넣어줌.
  • onClick 함수에 타입스크립트 타입으로 이벤트를 정의해주고 event를 통해서 버튼의 name을 받아오게 하면 됨.
    위의 방식으로 해도 똑같은 방법임!

  • ES6 문법으로 바꿔주고, 어떤 toDo를 수정해야 하는지 알 수 있게 id를 받아옴.

이제 수정하고 싶은 toDo의 id를 알고, 어느 카테고리로 가야하는지도 알 수 있게 세팅이 완료됨!

6.14 불변성(Immutability) 1

이제 특정 to do의 카테고리를 바꿔보자.

  • console.log(toDos)를 했을때 위와 같이 input창에 입력한 값을 text로 가지는 배열임을 알 수 있음.
  • 위와 같이 text:3을 가진 to do의 index는 2인건 아는데, 이것의 category를 수정하려면 어떻게 해야할까?(state를 mutate하면 안되고 새로운 state를 만들어야함)

그럼 수정하고자 하는 to do의 경로를 찾아보자!
첫번째 단계는 id로 to do를 찾아야 함. 3을 가진 to do의 index를 알고 있으니 array 안에 있는 object의 index를 찾는 방법만 알면 됨.

  • setToDo(수정함수)를 선언해줌.
    • setToDos를 사용하면 값을 즉시 변경하거나 현재 값(혹은 oldToDos)을 argument로 주는 function을 만들 수 있음.
      ⭐️ oldToDos가 배열이니 .findIndex() 메소드를 사용해서 조건을 만족하는 to do의 index를 찾아주자.
    • toDo의 id가 props에서 오는 id와 같은지 비교하고 있음.
  • console.log(targetIndex)로 3을 가진 todo의 index를 잘 가져오는 것을 확인함.

두번째 단계는 새로운 to do를 만들어서 새 category로 새로운 to do를 만들어야함.

  • oldToDo와 newToDo를 각각 선언해줌
  • oldToDo에는 oldToDos 배열중 클릭한 타겟의 요소만 할당되게 함.
  • newToDo에는 새로운 객체를 만들어 기존의 to do하고 똑같은 prop을 가지게 하고 category에는 클릭된 버튼의 name을 할당되게 함.
    ->{text:text, id:id, category: name}이랑 같은 뜻임.


세번째 단계는 위의 oldToDos 배열에서 text:3을 가진 to do원소를 newToDo로 바꿔줄거임.
불변성을 추구하고 mutate를 하고싶지 않다면 새로운 array를 만들면 됨.

#6.15 ~ 6.18 2023.04.25 (화)

6.15 불변성(Immutability) 2

이번 강의에서는 배열의 원소를 어떻게 교체하는지에 관한 이론을 배워보자.
원소를 교체하는 이유는 원소의 위치가 바뀌지 않길 바라기 때문임. 하나를 지우고 맨 뒤에 새로운 걸 붙이는 방식은 좋지 않음.

⭐️ 배열의 원소를 교체하는 한가지 예시

  • 위의 배열에서 "mango"원소를 "감"으로 바꾸고 싶을때
    1. "mango"의 위치를 구하기 (target = 1)
    2. 배열을 원소의 이전과 이후로 두 부분으로 나누기
    3. 새 배열에 front와 새로운 원소, back으로 합쳐주기 ( [...front, "새로운 원소", ...back])
      이렇게 하면 "mango"의 원래 자리에 새 원소인 "감"으로 교체가 됨!

이제 위의 예시를 그대로 todo리스트에 대입해보자.


위와 같이 return 해주면 선택한 원소가 newToDo로 교체된걸 확인 할 수 있음.

  • 타입스크립트 에러가 나는 문제가 있음. category가 "TO_DO" | "DOING" | "DONE" 중에 하나여야 하는데, 그냥 string이라 나는 에러임.
    • 이 문제를 회피해버리는 방법은 category:name뒤에 as any라고 적으면 타입스크립트에게 체크하지 말라고 할 수 있음.
    • 지금은 버튼의 name을 이용하는 방식이라서 어쩔 수 없이 위의 방법을 사용해야 하고, 보통은 전에 했었던 각각의 onClick에 문자열을 보내는 방식을 더 선호함.

결과화면 ↓

6.16 Selectors part One

recoil의 selector라는 개념에 대해 배워보자.

Selector 공식문서

  • derived state를 나타냄
  • state를 입력 받아서 그걸 변형해 반환하는 순수함수를 거쳐 반환된 값
  • atom을 가져다가 output을 변형시키는 도구

  • selector는 key와 get을 가짐.
  • get function은 인자로 객체를 받으며, 그 객체에는 get function이 들어가 있음.
  • return하는 값이 바로 toDoSelector의 value가 됨.
  • useRecoilValue()을 이용해서 selector의 value를 구할 수 있음.


지금 todo state에는 카테고리와 상관없이, 모든 todo들이 저장되고 있는 상황임. 하지만 카테고리에 맞춰 3개의 state를 만드는건 비효율적임.

그래서 이번 강의에서 할 것은, selector를 이용해서 이 todo들을 카테고리에 따라서 분류를 해보자!

  • 위와 같이 get function으로 todo state를 가져옴.
  • filter함수를 이용해, 3개의 배열을 담은 배열을 return함.
  • 각각의 원소에는 카테고리에 속한 todo가 모두 담긴 배열임.
    [["TO_DO"에 속한 todo], ["DOING"에 속한 todo], ["DONE"에 속한 todo]]

이제 각각의 카테고리에 있는 toDo를 한 화면에 렌더링 되게 코드를 바꿔주자.

  • toDos,doing,done 변수에 배열안의 3개의 배열을 하나씩 할당해줌.
  • 배열들을 map함수로 하나씩 매핑시켜서 렌더링되게 해줌.
  • selector가 분류해주고 있음.

결과화면 ↓

6.17 Selectors part Two

이번 강의에서는 3개의 카테고리를 한번에 렌더링하지 않고, 한번에 1개의 카테고리만 보여지게 만들어 볼거임.

그러면 사용자가 현재 선택한 카테고리를 저장할 새로운 state를 만들어 보자.

  • 위와 같이 select state를 만듦.
  • ToDoList.tsx에 원래 있었던 코드를 지워주고 select 태그를 만들어줌.
    • select의 change event는 감지하지 않을 거라서 onInput 이벤트 함수를 만들어줌.
    • console.log()로 value를 확인해줌.

이제 셀렉트에서 선택된 옵션의 value를 가져올 수 있는것을 확인했고, 이 value를 category state atom과 연결시켜 보자!

  • useRecoilState()로 atom의 value값과 수정함수를 만들어 줌.
  • input이 변할 때, setCategory 수정함수에 selct의 선택된 value값을 넣어줌.
  • select에 value 넣는 이유는 시작하는 값을 정하기 위해서임. 아래의 댓글 참고하기 ↓
    (select에 value 넣는 이유는 시작하는 값이에요.

option에 To do, Doing, Done 순으로 하셔서 필요성을 못느끼셨겠지만 Doing, Done, To do 순으로 하셔도
select value = "TO_DO"로 되있기 때문에 처음 선택되어 있는 값은 To do 입니다. 시작값, 말그대로 default value라고 생각하시면 됩니다 !

그래서 select에 value를 넣어주지 않고 option의 순서를 바꾸게 되면 처음 페이지에 들어왔을 때 category !== option의 value 일 수 있습니다.

select의 value를 없애시고 option의 순서를 바꿔보시면서 category를 console로 찍어보시면 이유를 아실 수 있을 거에요.)

이제 연결이 완료됨!

다음은 category가 "TO_DO"면 toDo만 render되고, "DOING"이면 doing만, "DONE"이면 "done"만 렌더링되게 해보자.

  • if/else로 구현함.
  • 위와 같이 코드를 짜도 잘 작동하지만 selector를 사용해서 간단하게 코드를 줄일 수 있음.

  • atoms.tsx파일에서 selector의 get함수로 category state를 가져옴.
    • 조건문으로 category 값에 따라서 이차원 배열이 아닌, 배열 하나만 리턴되게 함.
  • 위와 같이 toDoselector가 에러가 나는건 더이상 이차원 배열이 아니라서.

  • toDoSelector의 변수를 바꿔주고, 하나의 배열만 리턴하니까 한줄의 코드로 toDos배열 하나로만 매핑되게함
  • 마지막으로 selector의 if문을 간단하게 한줄로 바꿔줄 수 있음!

결과화면↓

6.18 Enums


CreateToDo 컴포넌트에서 새 toDo를 추가 할때마다, "TO_DO" 카테고리로만 들어가고 있음.
-> 이번 강의에서는 categoryState에 따라서, 새 toDo의 카테고리가 정해지도록 고쳐보자!

  • useRecoilValue()로 현재 카테고리를 얻어오고 setToDos 수정함수에 category: category로 변수를 넣어줌
    • 단축 문법으로는 category: category === category 동일함


에러가 생기는 이유는 category는 그냥 string인데, toDo의 category는 세 종류로 제한되기 때문임.

  • atoms.tsx파일로 가서 위와 같이 타입스크립트에게 categoryState가 세 개 중의 하나일 거라고 알려주면 해결됨!
  • 위의 코드는 반복되고 있어서 더 간단하게 고칠 수 있음.


기본적으로 재사용이 가능한 type이란 걸 만들어서 반복되는 코드를 고침.

  • category state의 타입을 만들어준것 때문에 ToDoList 컴포넌트에 에러가 났는데, setCategory 함수가 categories 타입만 받게 되었기 때문임.
    • select에서 setCategory 함수를 호출할 때, 인자로 타입이 string인 값을 넘기고 있음.
    • 타입스크립트가 보기에 option의 value는 그냥 string임


이 문제를 해결하려면,as any만 적어주면 됨! 별로 좋은 방법은 아니지만 일단 이렇게 두고 나중에 고쳐볼거임.

  • 이제 새 todo가 카테고리에 기반해서 추가가 되고 있음!
  • prop들을 이리저리 보내지 않아도 되고, 필요할 때 atom을 가져오고, 또 필요할 때 그걸 수정할 수 있다는 건 효율적.

이번엔 카테고리 type 방식을 고쳐 볼거임.

  • 위와 같이 type도 쓰고 "TO_DO"또 쓰고 있는데 이런식으로 통일되지 않은건 좋은 방법이 아님. type은 복붙을 안 하게 해주는 단순한 문법일뿐.
  • 지금 원하는건 "TO_DO" | "DOING" | "DONE";을 실수를 방지하기 위해 코드 전체에서 각각 한개씩 사용할 수 있게 고치는 것임.
    • 그럼 enum을 만들어 보자

enum은 enumerable 이라는 뜻으로 이제 모든 category의 타입에는 이것을 쓸 거임.

  • 원하는 string들로 enum을 만들어서 원래의 categories type이 쓰이는 곳에 바꿔줌.
  • default에서 오류가 나는건 겉보기엔 enum 타입과 같아 보이지만 string으로 인식하기 때문.
    • Categories.TO_DO로 바꿔주면됨.
      이런 식으로 만들면 string을 직접 적을 필요가 없어서 실수를 방지할 수 있음!



ToDoList.tsx로 가서 value에 적혀있던 string도 고쳐주자.

  • categories 타입을 바꿔주니 ToDo.tsx에도 오류가 생김
    • category는 enum Categories 타입인데, 이걸 string 타입과 비교하고 있기 때문임.

  • 위와 같이 고쳐주고, name에서 나는 오류는 enum 때문에 나는 오류임.

  • atoms.tsx에 가서 Categories를 살펴보면, Categories["TO_DO"]의 값은 사실 0이고 나머지 는 각각 1,2로 숫자임.
  • Categories.DOING이 아닌 category를 선택하려고 할 때, 실은 category가 1이 아니라는 걸 확인하고 있는 거임.
  • 기본적으로 enum은 개발자들을 도와주기 위해서 일련의 숫자를 문자로 표현해줌!
    • 당장은 이렇게 오류를 고칠 수 있음.{Categories.DOING + ""}

  • todo를 conosole.log()로 확인해 봤을때, category가 0으로 되어 있고, 모두가 같은 enum을 참조하기 때문에 문제없이 작동함.
    • 코드 상에서는 "TO_DO","DOING","DONE"의 실제 값이 0인 것임.

이제 enum을 원한다면 string으로 인식되게 바꿔볼거임.

  • 위와 같이 enum을 바꿔주면 숫자가 아닌 string으로 인식됨.

💡 enum type을 정하는 방식은 개발자가 데이터가 어떤 모습이기를 원하는지에 달렸음. 예를 들어 database에서는 숫자형식이 더 도움이 될 수도 있으니 원하는 대로 사용하면 됨!

profile
Frontend Developer

0개의 댓글