[TIL] TS주차 첫 번째 개인과제

곽재훈·2024년 6월 25일
4
post-thumbnail

TS주차 개인과제!


파일 분리하기

타입 파일

export interface Country {
  name: {
    common: string;
  };
  capital: string[];
  flags: {
    svg: string;
  };
}

export interface CountryInfo {
  id: string;
  name: string;
  capital: string;
  isFavorite: boolean;
  flagUrl: string;
}

위에 interface Country는 API에서 받아오는 데이터를 위한 type이고, interface CountryInfouseQuery에서 queryFnselect옵션을 거친 후 return되는 데이터를 위한 type이다.


API 파일

// api.countries.ts
export const getData = async () => {
  const response = await fetch("https://restcountries.com/v3.1/all");
  const data = await response.json();
  return data;
};

useEffect를 통한 비동기처리를 useQuery로 바꾸기!

  • Typescript로 간단한 Favorite List 만들기!

기존 useEffect를 통한 비동기처리

// App.jsx
useEffect(() => {
    const getCountryList = async () => {
      const data: Country[] = await getData();
      const countries: CountryInfo[] = data.map((country) => ({
        id: crypto.randomUUID(),
        name: country.name.common,
        capital: country.capital?.[0],
        flagUrl: country.flags.svg,
        isFavorite: false,
      }));
      setCountries(countries);
    };
    getCountryList();
  }, []);

useQuery를 통한 비동기처리

// App.tsx
const { data, isLoading } = useQuery<Country[], Error, CountryInfo[]>({
    queryKey: ["countries"],
    queryFn: getData,
    select: (data: Country[]) => {
      return data.map((country) => ({
        id: crypto.randomUUID(),
        name: country.name.common,
        capital: country.capital?.[0],
        flagUrl: country.flags.svg,
        isFavorite: false,
      }));
    },
  });
useEffect(() => {
  if (data) setCountries(data);
}, [isLoading]);


useQueryGeneric에는 네 개의 타입 파라미터를 넣을 수 있다!
첫 번째 타입 파라미터로는 queryFn에서 return되는 데이터의 타입을 넣어주고, 두 번째 타입 파라미터로는 Error의 타입을 넣어준다! 이 두 개가 필수인 듯 하다!
세 번째는 useQuery에서 select옵션을 사용하면 select에서 반환되는 return 타입을 별도로 적어주는 듯 하다!

나는 queryFn에서 return되는 자료를 그대로 쓰지 않고 가공해서 쓰고 싶었다. 그래서 useQuery에서 select 옵션을 쓰고 있었기 때문에 세 번째 제네릭 타입까지 추가했다!


App.tsx

import { useQuery } from "@tanstack/react-query";
import { useEffect, useState } from "react";
import "./App.css";
import { getData } from "./api/api.countries";
import CountryCardList from "./components/CountryCardList";
import { Country, CountryInfo } from "./types/country";

function App() {
  const [countries, setCountries] = useState<CountryInfo[]>([]);
  const handleToggleIsFavorite = (id: CountryInfo["id"]) =>
    setCountries((prev) =>
      prev.map((country) =>
        country.id === id
          ? { ...country, isFavorite: !country.isFavorite }
          : country
      )
    );

  const { data, isLoading } = useQuery<Country[], Error, CountryInfo[]>({
    queryKey: ["countries"],
    queryFn: getData,
    select: (data: Country[]) => {
      return data.map((country) => ({
        id: crypto.randomUUID(),
        name: country.name.common,
        capital: country.capital?.[0],
        flagUrl: country.flags.svg,
        isFavorite: false,
      }));
    },
  });
  useEffect(() => {
    if (data) setCountries(data);
  }, [isLoading]);

  if (isLoading) return <>Loading...</>;

  return (
    <>
      <CountryCardList
        listTitle="My Favorite Countries"
        countries={countries}
        handleToggleIsFavorite={handleToggleIsFavorite}
        isFavorite={true}
      />
      <CountryCardList
        listTitle="All Countries"
        countries={countries}
        handleToggleIsFavorite={handleToggleIsFavorite}
        isFavorite={false}
      />
    </>
  );
}

export default App;

CountryCardList.tsx

import { CountryInfo } from "../../types/country";
import CountryCard from "./CountryCard";
import ListTitle from "./ListTitle";

type CountryCardListProps = {
  countries: CountryInfo[];
  handleToggleIsFavorite: (id: string) => void;
  isFavorite: boolean;
  listTitle: string;
};

function CountryCardList({
  countries,
  handleToggleIsFavorite,
  isFavorite,
  listTitle,
}: CountryCardListProps) {
  return (
    <>
      <ListTitle>{listTitle}</ListTitle>
      <ul className="grid sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-8">
        {countries
          .filter((item) => item.isFavorite === isFavorite)
          .map((item, i) => {
            return (
              <li key={i}>
                <CountryCard onClick={handleToggleIsFavorite} {...item} />
              </li>
            );
          })}
      </ul>
    </>
  );
}

export default CountryCardList;

CountryCard.tsx

import { CountryInfo } from "../../types/country";

interface CountryCardProps extends CountryInfo {
  onClick: (id: string) => void;
}

function CountryCard({
  id,
  onClick,
  name,
  flagUrl,
  capital,
}: CountryCardProps) {
  return (
    <div
      onClick={() => onClick(id)}
      className={`box-border rounded-xl flex flex-col items-center p-4 w-full h-full gap-4 shadow-md hover:shadow-lg bg-[#FCFDFF]`}
    >
      <img className="object-cover w-24 " src={flagUrl} alt="국기" />
      <h3 className="self-start text-lg font-semibold ">{name}</h3>
      <p className="self-start text-md">{capital}</p>
    </div>
  );
}

export default CountryCard;
profile
개발하고 싶은 국문과 머시기

0개의 댓글

관련 채용 정보