TIL #48 플러스주차 개인과제

DO YEON KIM·2024년 6월 26일
1

부트캠프

목록 보기
48/72

하루 하나씩 작성하는 TIL #48



🔥 필수 구현 사항 입니다.
  • vite, react, typescript 기반의 프로젝트를 시작합니다.
  • 제공된 API 를 호출하는 로직을 작성하고 적절한 타입을 사용합니다.
  • API 의 응답 값을 컴포넌트에서 useState 를 이용해 상태관리를 해줍니다. 적절한 타입이 꼭 명시되어야 합니다.
  • useState 에서 상태관리되고 있는 값들을 화면에 보여주고, 사용자와 인터렉션 (선택/해제) 가 가능하도록 합니다. 이 과정에서 적절한 타입이 명시되어 있는 함수를 사용하도록 합니다.
💪 선택 구현 사항 : 필수 구현 사항을 모두 완료하고 나서 여유가 되신다면 시도해 보세요! 꼭 순서대로 하시지 않으셔도 괜찮습니다.
  • 보여준 데이터를 Sort 할 수 있는 함수들을 작성해보세요.
  • Supabase 에 선택되어 있는 나라들을 저장 할 수 있는 로직을 작성해봅시다. 단, API 에서 받아온 데이터는 필요없는 데이터도 많은 것 같아요. 우리가 필요한 정보들만 따로 모아서 새로운 Country 타입을 설정하고 그 값을 저장하도록 합시다.
⛳ 과제 진행 순서 (권장)
  • (1) 프로젝트 셋업

    1. vite 를 이용해 리액트 + 타입스크립트 프로젝트를 생성합니다.

    2. 이제는 사용법을 스스로 찾아보는 능력도 중요해요. 아래 링크에 가서 한번 직접 시도해 봅시다!!

      링크 - https://vitejs.dev/guide/

  • (2) API 호출 설정을 하도록 합니다.

    • 상세가이드
      1. src/api 폴더를 만들고 다음 API 를 호출하는 로직을 작성합니다. 간단히 ‘GET’ 메서드를 이용해 호출해주세요. API URL : https://restcountries.com/v3.1/all
      2. API 응답값을 확인하고 응답값에 대한 타입을 정해주도록 합시다.
        1. src/types 에 해당 타입에 대한 파일을 만들도록 합시다. (권장)
      3. API 를 불러오는 로직을 담당하는 함수와 응답값에 모두 적절한 타입을 넣어주어야 합니다.
  • (3) CountryList컴포넌트 작성

    • 상세가이드
      1. 위 API 는 세계 나라들에 대한 기본 정보를 제공해주는 API 입니다. 해당 API 에서 받아온 각 나라들에 대한 기본 정보를 보여줄 리스트 컴포넌트를 만듭시다. 예시) CountryList.tsx
      2. CountryList 컴포넌트는 App.tsx 에서 렌더링 되도록 합시다.
      3. ‘2’ 번에서 만든 API 를 호출하고, useState 를 이용해 해당 response 를 CountryList 안에서 상태관리를 해주도록 합시다.
      4. 위 모든 로직에서 적절한 type 사용이 이뤄져야 합니다.
  • (4) CountryCard컴포넌트 작성

    • 상세가이드
      1. CountryList 컴포넌트에서 map 을 이용해 각 useState 에 상태관리되고 있는 각나라에 대한 정보를 렌더링 해주도록 합시다. CountryCard 컴포넌트를 작성해주세요.
      2. CountryCard 컴포넌트는 개별 나라에 대한 정보를 props 로 받아와서 보여주는 역할을 담당합니다.
      3. 위 모든 로직에서 적절한 type 사용이 이뤄져야 합니다.
  • (5) 추가 로직 작성

    • 상세가이드
      1. CountryCard 가 클릭이 되었을 때, 클릭 된 CountryCard 를 저장하고 있을 수 있는 state 를 하나 더 생성해줍니다. 예) [selectedCountries, setSelectedCountries]
      2. CountryCard 를 클릭하면 selectedCountries 에 해당 나라정보를 등록해주고, 다시 한번 클릭된다면 제거가 되도록 합시다.
      3. ‘2’ 번의 로직을 위해 꼭 함수를 생성하도록 하고, 적절한 타입을 사용하도록 합니다.
  • (6) 배포

    • Vercel 을 이용해 배포해주세요.

1. 프로젝트 셋업

1. vite 를 이용해 리액트 + 타입스크립트 프로젝트를 생성합니다.


yarn create vite
Project name: ... vite-project
√ Target directory "vite-project" is not empty. Please choose how to proceed: » Remove existing files and continue
√ Select a framework: » React
√ Select a variant: » TypeScript + 
 yarn install

2. API 호출 설정

1. src/api 폴더를 만들고 다음 API 를 호출하는 로직을 작성합니다. 간단히 ‘GET’ 메서드를 이용해 호출해주세요. API URL : https://restcountries.com/v3.1/all


src/api/countries.js

// src/api/countries.ts
import axios from 'axios';
import { Country } from '../types/Country';

const API_URL = 'https://restcountries.com/v3.1/all';

export const getCountries = async (): Promise<Country[]> => {
    const response = await axios.get<Country[]>(API_URL);
    console.log('API 응답 데이터:', response.data);  // API 응답 데이터를 콘솔에 출력

    const countries = response.data.map(country => ({
        name: country.name,
        capital: country.capital,
        flags: country.flags,
    }));

    console.log('변환된 데이터:', countries);  // 변환된 데이터를 콘솔에 출력
    return countries;
};

코드 설명


async (): Promise<Country[]>

async -> Promise 반환 비동기 함수, await 키워드 사용가능 떠올려주기.


함수의 반환 타입을 명시. Country 타입의 배열을 반환하는 Promise를 반환.


= 비동기 작업이 완료되면 Country 객체의 배열을 반환.


const response = await axios.get<Country[]>(API_URL);

await 키워드 -> 비동기 작업이 끝날 때 까지 기다려주기


= axios.get 호출이 완료될 때까지 기다리기


axios.get 메서드를 사용하여 http get 메서드 요청 보내기.


<Country[]>는 제네릭 타입으로 이 요청이 Country 타입의 배열을 반환할 것임을 나타냄.


=>이 제네릭 타입 덕분에, response.data가 Country[] 타입으로 자동 추론됨.


const countries = response.data.map(country => ({
    name: country.name,
    capital: country.capital,
    flags: country.flags,
}));

map메서드 내에 각 요소는 country로 참조. 여기서 country는 Country 타입 객체 (response.data는 Country[] 타입이기 때문.)
map 함수 내에서 각 country를 객체로 변환하여 새로운 객체를 생성. 기존 country 객체의 capital 속성을 새 객체의 name으로 복사. 이하동문.
---

2. API 응답값을 확인하고 응답값에 대한 타입을 정해주도록 합시다.

  1. src/types 에 해당 타입에 대한 파일을 만들도록 합시다. (권장)

  2. API 를 불러오는 로직을 담당하는 함수와 응답값에 모두 적절한 타입을 넣어주어야 합니다.


    src\types\Country.ts

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



코드 설명


예시 사이트에선 국가와 수도, 이미지만 불러오기 때문에 3개만 불러와준다.


인터페이스는 TypeScript에서 타입을 정의하는 한 방법.
인터페이스는 객체가 특정 속성과 메서드를 가질 것을 강제할 수 있다.
= 인터페이스는 객체의 "형태"를 정의


3. CountryList컴포넌트 작성


  1. 위 API 는 세계 나라들에 대한 기본 정보를 제공해주는 API 입니다. 해당 API 에서 받아온 각 나라들에 대한 기본 정보를 보여줄 리스트 컴포넌트를 만듭시다. 예시) CountryList.tsx

import React, { useEffect, useState } from 'react';
import { getCountries } from '../api/countries';
import { Country } from '../types/Country';
import CountryCard from './CountryCard';

const CountryList: React.FC = () => {
    const [countries, setCountries] = useState<Country[]>([]);
    const [selectedCountries, setSelectedCountries] = useState<Country[]>([]);

    useEffect(() => {
        const loadCountries = async () => {
            const data = await getCountries();
            setCountries(data);
        };
        loadCountries();
    }, []);

    const toggleCountrySelection = (country: Country) => {
        if (selectedCountries.some(c => c.name.common === country.name.common)) {
            setSelectedCountries(selectedCountries.filter(c => c.name.common !== country.name.common));
            setCountries([...countries, country]);
        } else {
            setSelectedCountries([...selectedCountries, country]);
            setCountries(countries.filter(c => c.name.common !== country.name.common));
        }
    };

    return (
        <div>
            <h2>찜한 나라</h2>
            <div>
                {selectedCountries.map((country) => (
                    <CountryCard
                        key={country.name.common}
                        country={country}
                        onClick={() => toggleCountrySelection(country)}
                    />
                ))}
            </div>
            <h2>전체 나라 목록</h2>
            <div>
                {countries.map((country) => (
                    <CountryCard
                        key={country.name.common}
                        country={country}
                        onClick={() => toggleCountrySelection(country)}
                    />
                ))}
            </div>
        </div>
    );
};

export default CountryList;


코드 설명


const CountryList: React.FC = () => {

React.FC(React.FunctionComponent)는 타입스크립트에서 함수형 컴포넌트를 정의할 때 사용.


함수형 컴포넌트의 props 타입을 명확하게 정의하고, 기본적인 props 타입을 자동으로 추가할 수있음.


React.FC는 기본적으로 children prop을 포함


= () =>는 CountryList 변수가 함수임을 나타내며, 이 함수는 TSX를 반환.
함수의 반환 값은 TSX 요소. 이는 React 컴포넌트가 렌더링할 내용을 나타냄.


const [countries, setCountries] = useState<Country[]>([]);
const [selectedCountries, setSelectedCountries] = useState<Country[]>([]);

초기값은 빈 배열, 타입은 Country[]



useEffect(() => {
    const loadCountries = async () => {
        const data = await getCountries();
        setCountries(data);
    };
    loadCountries();
}, []);

useEffect 훅은 컴포넌트가 마운트될 때 실행


데이터를 가져온 후, setCountries 함수를 사용하여 countries 상태를 업데이트


빈 배열([])을 두 번째 인수로 전달하여, 이 훅이 컴포넌트가 처음 마운트될 때 한 번만 실행되도록 함.



const toggleCountrySelection = (country: Country) => {
    if (selectedCountries.some(c => c.name.common === country.name.common)) {
        setSelectedCountries(selectedCountries.filter(c => c.name.common !== country.name.common));
        setCountries([...countries, country]);
    } else {
        setSelectedCountries([...selectedCountries, country]);
        setCountries(countries.filter(c => c.name.common !== country.name.common));
    }
};

country: Country는 함수의 매개변수로, 선택되거나 선택 해제될 국가 객체를 나타냄.
이 매개변수는 Country 타입


some 메서드는 배열의 요소 중 하나라도 주어진 조건을 만족하면 true를 반환.


if (selectedCountries.some(c => c.name.common === country.name.common)) {

selectedCountries 배열의 각 요소(c)를 순회하면서, 해당 국가의 name.common 속성이 country 객체의 name.common 속성과 동일한지 확인.


= selectedCountries 배열에 country 객체가 이미 포함되어 있는지 확인.


setSelectedCountries(selectedCountries.filter(c => c.name.common !== country.name.common));
setCountries([...countries, country]);

filter 메서드는 배열의 요소를 순회하면서 주어진 조건을 만족하는 요소들로 새로운 배열을 만듦.

country 객체를 제외한 나머지 국가들로 새로운 배열을 만듦.

스프레드 연산자를 이용하여 country 객체를 countries 배열에 다시 추가


setSelectedCountries([...selectedCountries, country]);
setCountries(countries.filter(c => c.name.common !== country.name.common));

스프레드 연산자를 사용하여 현재 selectedCountries 배열의 모든 요소를 새로운 배열에 복사한 후, country 객체를 추가.


= country 객체를 selectedCountries 배열에 추가.


countries 배열의 각 요소(c)를 순회하면서, 해당 국가의 name.common 속성이 country 객체의 name.common 속성과 다른지 확인.


= country 객체를 제외한 나머지 국가들로 새로운 배열을 만듭니다.



return (
    <div>
        <h2>찜한 나라</h2>
        <div>
            {selectedCountries.map((country) => (
                <CountryCard
                    key={country.name.common}
                    country={country}
                    onClick={() => toggleCountrySelection(country)}
                />
            ))}
        </div>
        <h2>전체 나라 목록</h2>
        <div>
            {countries.map((country) => (
                <CountryCard
                    key={country.name.common}
                    country={country}
                    onClick={() => toggleCountrySelection(country)}
                />
            ))}
        </div>
    </div>
);



useEffect(() => {
    const loadCountries = async () => {
        const data = await getCountries();
        setCountries(data);
    };
    loadCountries();
}, []);

선택된 상태이면:
selectedCountries 배열에서 해당 국가를 제거하고, countries 배열에 다시 추가.


선택되지 않은 상태이면:
selectedCountries 배열에 해당 국가를 추가하고, countries 배열에서 제거




  1. CountryList 컴포넌트는 App.tsx 에서 렌더링 되도록 합시다.
import React from 'react';
import CountryList from './components/CountryList';
import './App.css';

const App: React.FC = () => {
    return (
        <div className="App">
            <h1>Countries of the World</h1>
            <CountryList />	//컴포넌트 렌더링
        </div>
    );
};

export default App;

  1. ‘2’ 번에서 만든 API 를 호출하고, useState 를 이용해 해당 response 를 CountryList 안에서 상태관리를 해주도록 합시다.

  1. 위 모든 로직에서 적절한 type 사용이 이뤄져야 합니다.


4 CountryCard컴포넌트 작성


  1. CountryList 컴포넌트에서 map 을 이용해 각 useState 에 상태관리되고 있는 각나라에 대한 정보를 렌더링 해주도록 합시다. CountryCard 컴포넌트를 작성해주세요.

import React from 'react';
import { Country } from '../types/Country';

interface CountryCardProps {
    country: Country;
    onClick: () => void;
}

const CountryCard: React.FC<CountryCardProps> = ({ country, onClick }) => {
    return (
        <div onClick={onClick} >
            <h2>{country.name.common}</h2>
            <p>Capital: {country.capital?.join(', ')}</p>
            <img src={country.flags.png} alt={`Flag of ${country.name.common}`} width="100" />
        </div>
    );
};

export default CountryCard;



코드 설명


interface CountryCardProps {
    country: Country;
    onClick: () => void;
}

props의 타입을 정의

country prop은 Country 타입의 객체여야 함.


onClick: () => void:

onClick prop은 인수가 없고 반환 타입이 void인 함수여야 함. 이 함수는 클릭 이벤트를 처리를 함.


({ country, onClick })

구조 분해 할당을 사용하여 country와 onClick props를 받아옴.


 return (
        <div onClick={onClick} >
            <h2>{country.name.common}</h2>
            <p>Capital: {country.capital?.join(', ')}</p>
            <img src={country.flags.png} alt={`Flag of ${country.name.common}`} width="100" />
        </div>
    );
};

export default CountryCard;

<p>Capital: {country.capital?.join(', ')}</p>

country.capital은 배열이므로, join(', ') 메서드를 사용하여 배열 요소를 쉼표와 공백으로 구분된 문자열로 변환.


?. 연산자는 선택적 체이닝(optional chaining) 연산자로, capital이 undefined일 경우 에러를 방지.


  1. CountryCard 컴포넌트는 개별 나라에 대한 정보를 props 로 받아와서 보여주는 역할을 담당합니다.

  1. 위 모든 로직에서 적절한 type 사용이 이뤄져야 합니다.

완성 모습은 위와 같다. 다음엔 css 작업으로 마무리 후, vercel로 배포하도록 하겠다.

profile
프론트엔드 개발자를 향해서

0개의 댓글