하루 하나씩 작성하는 TIL #48
(1) 프로젝트 셋업
vite 를 이용해 리액트 + 타입스크립트 프로젝트를 생성합니다.
이제는 사용법을 스스로 찾아보는 능력도 중요해요. 아래 링크에 가서 한번 직접 시도해 봅시다!!
(2) API 호출 설정을 하도록 합니다.
GET
’ 메서드를 이용해 호출해주세요. API URL : https://restcountries.com/v3.1/all
(3) CountryList
컴포넌트 작성
CountryList.tsx
CountryList
컴포넌트는 App.tsx 에서 렌더링 되도록 합시다.CountryList
안에서 상태관리를 해주도록 합시다. (4) CountryCard
컴포넌트 작성
CountryList
컴포넌트에서 map 을 이용해 각 useState 에 상태관리되고 있는 각나라에 대한 정보를 렌더링 해주도록 합시다. CountryCard
컴포넌트를 작성해주세요.CountryCard
컴포넌트는 개별 나라에 대한 정보를 props 로 받아와서 보여주는 역할을 담당합니다.(5) 추가 로직 작성
CountryCard
가 클릭이 되었을 때, 클릭 된 CountryCard
를 저장하고 있을 수 있는 state 를 하나 더 생성해줍니다. 예) [selectedCountries, setSelectedCountries]
CountryCard
를 클릭하면 selectedCountries 에 해당 나라정보를 등록해주고, 다시 한번 클릭된다면 제거가 되도록 합시다.(6) 배포
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
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,
}));
src/types 에 해당 타입에 대한 파일을 만들도록 합시다. (권장)
API 를 불러오는 로직을 담당하는 함수와 응답값에 모두 적절한 타입을 넣어주어야 합니다.
src\types\Country.ts
export interface Country {
name: {
common: string;
};
capital?: string[];
flags: {
svg: string;
png: string;
};
}
예시 사이트에선 국가와 수도, 이미지만 불러오기 때문에 3개만 불러와준다.
인터페이스는 TypeScript에서 타입을 정의하는 한 방법.
인터페이스는 객체가 특정 속성과 메서드를 가질 것을 강제할 수 있다.
= 인터페이스는 객체의 "형태"를 정의
CountryList
컴포넌트 작성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 배열에서 제거
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;
CountryList
안에서 상태관리를 해주도록 합시다.CountryCard
컴포넌트 작성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일 경우 에러를 방지.
CountryCard
컴포넌트는 개별 나라에 대한 정보를 props 로 받아와서 보여주는 역할을 담당합니다.완성 모습은 위와 같다. 다음엔 css 작업으로 마무리 후, vercel로 배포하도록 하겠다.