개인공부/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로 된다는 점입니다. 타입을 새로 만들어서 추가하면 되는 부분인 것 같습니다.