#TIL 43일차(TypeScript 개인 과제)

앙꼬·2024년 7월 4일

부트캠프

목록 보기
42/59


개인 과제

주제

API 를 호출하고 받은 응답값을 화면에 보여주는 과정에서 타입스크립트를 사용해봅시다.

요구 사항

  • vite, react, typescript 기반의 프로젝트를 시작합니다.
  • 제공된 API 를 호출하는 로직을 작성하고 적절한 타입을 사용합니다.
  • API 의 응답 값을 컴포넌트에서 useState 를 이용해 상태관리를 해줍니다. 적절한 타입이 꼭 명시되어야 합니다.
  • useState 에서 상태관리되고 있는 값들을 화면에 보여주고, 사용자와 인터렉션 (선택/해제) 가 가능하도록 합니다. 이 과정에서 적절한 타입이 명시되어 있는 함수를 사용하도록 합니다.

제출 코드

country.ts

export type Country = {
  name: {
    common: string;
  };
  capital: string[];
  flags: {
    png: string;
  };
};

export type SelectedCountry = Country & {
  isSelected: boolean;
};
  • Country 타입을 정의한다.
    • 이는 API에서 반환되는 국가 정보를 나타낸다.
  • SelectedCountry 타입은 Country 타입을 확장하여 추가적으로 isSelected 속성을 포함한다.
    • 이는 사용자가 국가를 선택했는지 여부를 나타낸다.

worldApi.ts

import axios from 'axios';
import { Country } from '../types/country';

const WORLD_API = "https://restcountries.com/v3.1/all";

export const getCountries = async (): Promise<Country[]> => {
  try {
    const response = await axios.get<Country[]>(WORLD_API);
    return response.data;
  } catch (error) {
    throw new Error('Failed to fetch countries data');
  }
}
  • axios를 사용하여 국가 정보를 가져오는 함수 getCountries를 정의한다.
  • API 호출 후, 응답 데이터를 Country 타입의 배열로 반환한다.
  • 에러 발생 시 적절한 에러 메시지를 던진다.

CountryCard.tsx

import React from "react";
import { SelectedCountry } from "../types/country";

interface CountryCardProps {
  country: SelectedCountry; // SelectedCountry 타입
  handleSelect: (country: SelectedCountry) => void; // 부모 컴포넌트에서 전달된 함수
}

const CountryCard: React.FC<CountryCardProps> = ({ country, handleSelect }) => {
  return (
    <li
      key={country.name.common}
      onClick={() => handleSelect(country)}
      className="flex-col drop-shadow-lg border rounded-lg m-4 p-4"
    >
        <img
          src={country.flags.png}
          alt={`${country.name.common} flag`}
          className="w-1/2 h-1/2 m-auto object-cover"
        />
      <div>
        <h3 className="text-lg font-medium mt-4">{country.name.common}</h3>
        <p className="text-sm">{country.capital}</p>
      </div>
    </li>
  );
};

export default CountryCard;
  • CountryCard 컴포넌트는 SelectedCountry 타입의 country와 선택 핸들러 handleSelect를 props로 받는다.
  • 국가 정보를 카드 형식으로 렌더링하며, 카드 클릭 시 handleSelect 함수를 호출한다.

CountryList.tsx

import React, { useEffect, useState } from "react";
import { SelectedCountry } from "../types/country";
import { getCountries } from "../api/worldApi";
import { AxiosError } from "axios";
import CountryCard from "./CountryCard";

const CountryList: React.FC = () => {
  const [countries, setCountries] = useState<SelectedCountry[]>([]);
  const [isLoading, setIsLoading] = useState<boolean>(true);
  const [error, setError] = useState<AxiosError | null>(null);

  useEffect(() => {
    const fetchCountries = async () => {
      try {
        const data = await getCountries();
        const selectedCountries = data.map((country) => ({
          ...country,
          isSelected: false,
        }));
        setCountries(selectedCountries);
      } catch (error) {
        if (error instanceof AxiosError) {
          setError(error);
        } else {
          console.error(error);
        }
      } finally {
        setIsLoading(false);
      }
    };

    fetchCountries();
  }, []);

  const handleSelect = (selectedCountry: SelectedCountry) => {
    setCountries((prevCountries) =>
      prevCountries.map((country) =>
        country.name.common === selectedCountry.name.common
          ? { ...country, isSelected: !country.isSelected }
          : country
      )
    );
  };

  if (isLoading) {
    return <div>로딩중...</div>;
  }

  if (error) {
    console.error(error);
    return <div>에러가 발생했습니다!!</div>;
  }

  return (
    <div className="w-3/4 flex flex-col items-center m-auto gap-2">
      <h1 className="font-semibold text-2xl">Favorite Countries</h1>
      <ul className="grid lg:grid-cols-4 md:grid-cols-3 sm:grid-cols-2">
        {countries
          .filter((country) => country.isSelected)
          .map((country) => (
            <CountryCard
              key={country.name.common}
              country={country}
              handleSelect={handleSelect}
            />
          ))}
      </ul>
      <h1 className="font-semibold text-2xl">Countries</h1>
      <ul className="grid lg:grid-cols-4 md:grid-cols-3 sm:grid-cols-2">
        {countries
          .filter((country) => !country.isSelected)
          .map((country) => (
            <CountryCard
              key={country.name.common}
              country={country}
              handleSelect={handleSelect}
            />
          ))}
      </ul>
    </div>
  );
};

export default CountryList;
  • CountryList 컴포넌트는 국가 목록을 가져와 상태로 관리한다.
  • API 호출을 통해 국가 데이터를 가져오고, 각 국가에 isSelected 속성을 추가한다.
  • 선택된 국가와 선택되지 않은 국가를 각각 다른 리스트로 렌더링한다.
  • 사용자가 국가를 선택/해제할 수 있도록 handleSelect 함수를 정의한다.

App.tsx

import CountryList from "./components/CountryList";

function App() {
  return (
    <div className="w-full flex">
      <CountryList />
    </div>
  );
}

export default App;
  • App 컴포넌트는 CountryList 컴포넌트를 렌더링한다.
  • CountryList 컴포넌트는 국가 목록과 사용자 인터렉션 기능을 제공한다.
profile
프론트 개발자 꿈꾸는 중

0개의 댓글