본문 바로가기
개인공부/React

Recoil 사용후기

by BTSBRINGMEHERE 2023. 4. 25.

서론

나 혼자 사이드 프로젝트를 만들던 중 다양한 상태관리를 사용하고 싶었습니다.
우선 React Query를 사용하기로 했습니다. 서버에서 데이터를 get하기 위해서 가장 정형화 되어있다고 생각해서 입니다.
그러던 중 React Query만으로는 최초 하나의 상태만 get하고 그 다음의 상태들이 제대로 들어오지 않아 추가적인 상태관리 툴인 recoil을 사용하기로 했습니다. 기존의 RTK을 사용했지만 RTK 역시 redux에 비해 상태관리가 쉬울 뿐 store, slice, hook 등 여러 곳에서 코드를 구성해야하기 때문에 이역시 복잡하다 생각하여 zustand, recoli 둘 중 무엇을 할까 고민했습니다.
처음에는 zustand를 활용했지만 기존의 데이터를 저장하면서 새로운 데이터를 추가하고자 했지만 미친듯한 에러가 발생해서 포기했습니다... 해당 데이터가 무한으로 들어가는~...
그래서 최종적으로 recoil을 선택을 하게되어 React Query와 recoil를 사용하기로 했습니다.

코드 구성

recoilStore.ts

import { atom } from "recoil";
import { IAllPokes, IPokemonDetail } from "../types/pokemonType";

export const pokemonState = atom<IAllPokes>({
  key: "pokemons",
  default: {
    count: 0,
    next: "",
    results: [
      {
        name: "",
      },
    ],
  },
});

export const pokemonDetailState = atom<IPokemonDetail>({
  key: "pokemonDetail",
  default: {
    id: 0,
    name: "",
    koreanName: "",
    color: "",
    image: "",
    flavor_text_entry: "",
    types: [],
  },
});

CardList.tsx

const [pokemons, setPokemons] = useRecoilState(pokemonState);

  const { isLoading } = useQuery("pokemons", () => pokemonAPI(""), {
    onSuccess: (data) => setPokemons(data),
  });
  
  {pokemons?.results.map((item, idx: number) => {
    return <Card name={item.name} key={`${item.name}_${idx}`} />;
  })}
  
  .
  .
  .

export default Card
전체 적인 리스트를 불러와서 해당 스토어에 리스트를 저장합니다.
해당 포켓몬 이름을 Card 컴포넌트의 props로 보냅니다.

Card.tsx

const Image = lazy(() => import("../common/image/Image"));

interface CardProps {
  name: string;
}

const Card = (props: CardProps) => {
  const [pokemon, setPokemon] = useRecoilState(pokemonDetailState);

  useQuery(["pokemonDetail", props.name], () => pokemonDetailAPI(props.name), {
    onSuccess: (data) => {
      setPokemon((prevPokemon) => ({
        ...prevPokemon,
        [props.name]: data,
      }));
    },
  });
  
  const pokemonData: IPokemonDetail = pokemon[props.name];
  
  return (
    <div className="sm:w-1/2 mb-10 px-4">
      <div className="rounded-lg h-64 overflow-hidden">
        <Image props={pokemonData.image} />
      </div>
      <h2 className="title-font text-2xl font-medium text-gray-900 mt-6 mb-3">
        {pokemonData.koreanName}
      </h2>
      <p className="leading-relaxed text-base">
        {pokemonData.types.map((type) => (
          <>{type} </>
        ))}
      </p>
      <p className="leading-relaxed text-base">
        {pokemonData.flavor_text_entry}
      </p>
      <button className="flex mx-auto mt-6 text-white bg-indigo-500 border-0 py-2 px-5 focus:outline-none hover:bg-indigo-600 rounded">
        Button
      </button>
    </div>
  )
};

export default React.memo(Card)
포켓몬 이름 props를 가져와서 해당 포켓몬의 이름에 맞는 URL을 찾고 recoil store에 하나씩 추가를 합니다.
기존에는 그냥 넣어서 최초에 받은 첫 번째 포켓몬만 주르륵 나왔지만 기존의 포켓몬을 저장하면서 추가해야하기 때문에 
useQuery에서 onSuccess할 때, 기존의 데이터를 저장하면서 새로운 값을 추가하는 방식입니다.

정리

진짜 너무 간편합니다. 최상위 컴포넌트에 RecoilRoot 로 묶어서 해당 컴포넌트 안에서 상태관리를 하고 여러가지 slice, store, hook 구성도 필요없고 zustand처럼 아직 초보자가 사용하기 불편한 점도 없었습니다.
아쉬운 점이 있다면
const pokemonData: IPokemonDetail = pokemon[props.name];
해당 부분이 타입이 any로 된다는 점입니다. 타입을 새로 만들어서 추가하면 되는 부분인 것 같습니다.

'개인공부 > React' 카테고리의 다른 글

Recoil Select 활용하기  (0) 2023.05.10
react-query + recoil  (0) 2023.05.02
Rules of Hooks  (0) 2023.03.29
Redux Toolkit ...  (0) 2022.12.22
React-infinite-scroll-hook  (1) 2022.11.18