so woon!

[RECOIL] Selector1 본문

Recoil/개념정리

[RECOIL] Selector1

xowoony 2023. 4. 20. 15:50

학습일 : 2023. 04. 20


 

selector는 derived state를 나타낸다.
derived state란, state를 입력 받아서

그걸 변형해 반환하는 순수함수를 거쳐 반환된 값을 말한다.

selector를 사용하면 state 자체가 변하는 것은 아니고

output을 변형하는 것이다.

selector가 있으면 데이터에 좀 더 체계화된 방식으로 접근할 수 있다.

한 곳에 데이터를 몰아넣고 컴포넌트 안에서 그것을 수정하는 대신

atom에 데이터를 모아두고

selector로 데이터를 변형할 수 있다.

 


<atoms.tsx>
우리는 지금 이 toDoState에 모든 todo들을 담고 있다.
카테고리와 상관없이, 모든 todo 들이 전부 같은 state에 저장되고 있다.
DONE, DOING, TODO 든 뭐든 간에 전부 한곳에 모여 섞여있단 뜻이다.

import { atom } from "recoil";

export interface IToDo {
  text: string;
  id: number;
  category: "TODO" | "DOING" | "DONE";
}
export const toDoState = atom<IToDo[]>({
  key: "toDo",
  default: [],
});




이젠, selector를 이용해서 이 todo들을 분류해보도록 하자.
다만, atom을 카테고리별로 각각 해서 3개로 늘리고 싶진 않다.
모든 todo들을 하나의 atom에 담고

그 atom의 output을 좀 더 잘 써먹을 수 있는 형태로 변형시켜보자.
(selector는 atom의 output을 변형시키는 도구라고 생각하면 된다.)


이는 selector function을 이용하면 되는데

 


<atoms.tsx>
selector를 만든다.
key가 필요함,
그리고 get function도 필요하다.

get은 options라는 인자를 받으면서 호출되는데, 

options는 객체이다. 그리고 그 객체에는 get function이 들어있다.

get: ({get}) => {}

 

import {atom, selector } from "recoil";

export interface IToDo {
  text: string;
  id: number;
  category: "TODO" | "DOING" | "DONE";
}

export const toDoState = atom<IToDo[]>({
  key: "toDo",
  default: [],
});

// selector
export const toDoSelector = selector({
  key:"toDoSelector",
  get: ({get}) => {
  return "hello";
 }
});




toDoSelector의 value를 잘 얻을 수 있는지 확인 해보도록 하자.
일단 toDoList.tsx로 가서 selectorOutput을 얻어보자

 

 


<ToDoList.tsx>
useRecoilValue() 를 이용해서 selector의 value를 구한다.

const selectorOutput = useRecoilValue(toDoSelector);
console.log(selectorOutput);



개발자 도구를 켜보면 hello가 찍혀나옴
이렇게 selectorOutput에 접근할 수 있다.

 


selector의 요점은 atom을 가져다가 output을 변형할 수 있다는 것이다.
이것이 get function이 있어야 하는 이유다.
get function이 있어야 atom을 받을 수 있다.


이제 모든 to do 들을 받을 것이다.

 

 


<atoms.tsx>
toDos를 선언하고 이 toDoState atom을 받도록 해보자
그리고 toDos.length를 리턴한다고 해보자
get function 을 이용하면 selector의 내부로 atom을 가지고 올 수 있다.

const toDos = get(toDoState);

export const toDoSelector = selector({
  key: "toDoSelector",
  get: ({ get }) => {
    const toDos = get(toDoState)
    return toDos.length;
  },
});



실행시켜보면
todo가 0개라고 나온다.



이렇게 하는 것이 좋은 것이 뭐냐면, toDoSelector가 atom을 보고 있다는 것이다.
따라서 atom이 변하면 selector도 변하게 된다는 것을 뜻한다.

 

 


예를 들면 
새 to do를 추가시키면 이렇게 console.log() 도 변하게 된다.




우리는 지금 ToDoList.tsx
에서 selector의 output을 console.log() 하고 있다는 걸 기억하자.

 


그리고 useRecoilValue()를 이용하여
atom의 output도, selector의 output도 얻을 수 있다는 것은 아주 중요하다.

 


 

배열을 리턴하고 싶은데, 그 배열은 다른 배열에 담겨져 있다.
[ [ { } , { }  ] ] 모양이라 할 수 있다.

어떻게 하면 될까?
filter function을 써볼 수 있겠는데,

filter function은 배열에서 조건에 맞지 않는 원소들을 제거한 배열을 리턴하게 한다.
즉 todo의 카테고리가 TODO와 같으면 남아있게 되는 것이고 아니면 버려지는 것이다.
두번째로는 DOING
마지막 배열은 toDo의 카테고리가 DONE 일 때

 

 


<atom.tsx>
filter function을 사용하여
카테고리가 TODO인 것만 만족하는 원소들만 담아서 리턴,
카테고리가 DOING인 것만 만족하는 원소들만 담아서 리턴,
카테고리가 DONE인 것만 만족하는 원소들만 담아서 리턴

export const toDoSelector = selector({
  key: "toDoSelector",
  get: ({ get }) => {
    const toDos = get(toDoState);
    return [
      toDos.filter((toDo) => toDo.category === "TODO"),
      toDos.filter((toDo) => toDo.category === "DOING"),
      toDos.filter((toDo) => toDo.category === "DONE"),
    ];
  },
});

 



실행시켜보면


selector의 output이 세 개의 빈 배열을 담은 한개의 배열이 되었다.



 




새 todo를 추가해보면



똑같이 3개의 배열을 담은 하나의 배열이지만
세 배열 중 첫번째 배열에 뭔가가 들어가 있다. 
그 배열이 TODO를 담는 배열이기 때문이다!


다른 항목도 추가해보겠당



TODO 배열에는 원소가 4개가 되었고, 
DOING과 DONE에는 없다.

이제 첫번째 todo를 DOING으로 바꿔보면



카테고리를 바꿔주면
알맞는 배열에 잘 들어가는 모습을 볼 수 있다.

 

이제 세 개의 각기 다른 배열을 render 해볼 것이다.

 



<ToDoList.tsx>
toDos를 render하는 대신 배열을 열 것이다
useRecoilValue(toDoSelector)의 return 값은 배열임을 기억해야 한다.
toDoSelector가 배열을 리턴한다는 것은
atoms.tsx 안에 있는 세 배열을 꺼내기 위해서 이 배열을 여는 것이다.

import { useRecoilValue } from "recoil";
import CreateToDo from "./CreateToDo";
import { toDoSelector, toDoState } from "./atoms";
import ToDo from "./ToDo";

function ToDoList() {
   // 배열 안의 배열을 선택하려면 이렇게 배열을 열고 순서대로 이름을 지정하면 된다.
  const [toDo, doing, done] = useRecoilValue(toDoSelector);

  return (
    <div>
      <h1>Thorn To Do</h1>
      <hr />
      <CreateToDo />
      <h2>TODO</h2>
      <ul>
        {toDo.map((toDo) => (
          <ToDo key={toDo.id} {...toDo} />
        ))}
      </ul>
      <hr />
      <h2>DOING</h2>
      <ul>
        {doing.map((toDo) => (
          <ToDo key={toDo.id} {...toDo} />
        ))}
      </ul>
      <hr />
      <h2>DONE</h2>
      <ul>
        {done.map((toDo) => (
          <ToDo key={toDo.id} {...toDo} />
        ))}
      </ul>
      <hr />
    </div>
  );
}

export default ToDoList;




실행결과
이제 기능 구현은 완료되었다.

 

 

 

Comments