6/26 TIL (TypeScript)

Hwiยท2024๋…„ 6์›” 26์ผ

TIL

๋ชฉ๋ก ๋ณด๊ธฐ
57/96

๐Ÿ“– ์ง„ํ–‰ํ•œ ๊ณต๋ถ€ ๐Ÿ“–

  • TypeScript ๊ฐ์ฒด ํƒ€์ž… ์ •์˜ ๋ฐ ์ œ๋„ค๋ฆญ
  • ๊ฐœ์ธ๊ณผ์ œ ์‹œ์ž‘

TypeScript ๊ฐ์ฒด ํƒ€์ž… ์ •์˜

type, interface๊ฐ€ ์žˆ๋Š”๋ฐ ๋‘˜์ด ํฐ ์ฐจ์ด๋Š” ์—†์œผ๋‚˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์„œ๋“œ๊ฐ€ ์กฐ๊ธˆ ๋‹ค๋ฅด๋‹ค๋Š” ์ 

type์œผ๋กœ ํƒ€์ž… ์ •์˜

// src/model/Restaurant.ts
export type Restaurant = {
	name: string;
  	category: string;
  	address: Address;
  	menu: Menu[]
};

export type Address = {
	city: string;
    detail: string;
    zipcode: number;
}

export type Menu = {
	name:string;
    price: number;
    category: string;
}

๊ฐ์ฒด๋Š” ํƒ€์ž…์„ ๋‹ค์‹œ ์žฌ์ •์˜๋ฅผ ํ•  ์ˆ˜ ์žˆ๊ธฐ์— address์™€ menu์— ๊ด€ํ•œ ํƒ€์ž…์„ ๋‹ค์‹œ ์žฌ์ •์˜ ํ•จ์œผ๋กœ์จ address ๋ฐ menu๊ฐ€ ํ•„์š”ํ•œ ๋‹ค๋ฅธ ๊ณณ์—์„œ๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค

์ฝ”๋“œ๊ฐ€ ํ›จ์”ฌ ๊น”๋”ํ•ด๋ณด์ž„

์ œ๋„ค๋ฆญ ๋ฌธ๋ฒ•

import {Restaurant} from "./model/restaurant"

const [myRestaurant, setMyRestaurant] = 
      useState<Restaurant>(data)

useState์„ ์„ ์–ธํ•  ๋‹น์‹œ์— Restaurant ๋ผ๋Š” ํƒ€์ž…์„ ์“ฐ๊ฒ ๋‹ค ๋ผ๊ณ  ์„ ์–ธํ•  ๋•Œ <> ์•ˆ์— ํ•ด๋‹น ํƒ€์ž…์„ ๋„ฃ์–ด์ฃผ๋Š” ์‹์œผ๋กœ ์‚ฌ์šฉ

interface๋กœ ํƒ€์ž… ์ •์˜

import {Restaurant} from "./model/restaurant"

interface OwnProps {
	info: Restaurant
}

const Store:React.FC<OwnProps> = ({info}) => {
	return (
    	<div>{info.name}</div>
    )
}

interface extends๋ž€?

ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•˜๋Š” ํด๋ž˜์Šค๊ฐ€ ์žˆ๋“ฏ์ด, ์ธํ„ฐํŽ˜์ด์Šค ๋˜ํ•œ
extends ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ™•์žฅ ํ•  ์ˆ˜ ์žˆ์Œ

์˜ˆ์‹œ

interface ButtonInterface {
  readonly _type:string;
  width?:number;
  height?:number;
  onInit?():void;
  onClick():void;
}
โ€‹
interface ButtonSizeInterface {
  readonly _size:number;
  small():void;
  medium():void;
  large():void;
  onChangeSize?():void;
}
โ€‹
// ButtonInterface, ButtonSizeInterface๋ฅผ ๋‹ค์ค‘ ํ™•์žฅํ•˜๋Š” ImageButtonInterface
interface ImageButtonInterface extends ButtonInterface, ButtonSizeInterface {
  readonly _url:string;
  getUrl():string;
  setUrl?(url:string):void;
  onChangeUrl?():void;
}

๊ฐœ์ธ๊ณผ์ œ ์‹œ์ž‘

๊ณผ์ œ์˜ ํ•„์ˆ˜ ๊ตฌํ˜„ ์‚ฌํ•ญ

getCountries.ts

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

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

export const getCountries = async (): Promise<Country[]> => {
 try {
   const res = await axios.get<Country[]>(baseUrl);
   console.log(res.data);
   return res.data;
 } catch (error) {
   console.error("Axios Get Error:", error);
   throw error;
 }
};

export default getCountries;

์ฒ˜์Œ์—” Promise ์˜†์— any ํƒ€์ž…์„ ์ ์–ด๋‘๊ณ  ํ›„์— Country.ts๋ฅผ ์ž‘์„ฑํ•œ ํ›„์— ์ˆ˜์ •ํ–ˆ์Œ

Country.ts

export interface Country {
   name: {
       common: string;
   }
   region: string;
   flags: {
       png: string;
   }
   ccn3: number;
};

๋‚˜๋ผ์— ๋Œ€ํ•ด ํ•„์š”ํ•œ ์ •๋ณด๋งŒ ํƒ€์ž… ์ •์˜

CountryList.tsx

import React, { useEffect, useState } from "react";
import styled from "styled-components";
import { Country } from "../types/Country";
import getCountries from "../api/getCountries";
import CountryCard from "./CountryCard";

const Container = styled.div`
  padding: 20px;
`;

const CountryGrid = styled.ul`
  display: flex;
  flex-wrap: wrap;
  list-style: none;
  padding: 0;
`;

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

  useEffect(() => {
    const fetchCountries = async () => {
      try {
        const data = await getCountries();
        setCountries(data);
      } catch (error) {
        alert(error);
      }
    };
    fetchCountries();
  }, []);

  const handleSelected = (country: Country): void => {
    if (
      !selectedCountries.find(
        (country: Country) => country.name.common === country.name.common
      )
    ) {
      setSelectedCountries([...selectedCountries, country]);
    } else {
      setSelectedCountries(
        selectedCountries.filter((country: Country) => {
          return country.name.common !== country.name.common;
        })
      );
    }
  };

  return (
    <Container>
      <h1>์ข‹์•„ํ•˜๋Š” ๋‚˜๋ผ</h1>
      <CountryGrid>
        {selectedCountries.map((country) => (
          <CountryCard
            key={country.ccn3}
            country={country}
            handleSelected={handleSelected}
          />
        ))}
      </CountryGrid>
      <h1>Countries</h1>
      <CountryGrid>
        {countries.map((country) => (
          <CountryCard
            key={country.ccn3}
            country={country}
            handleSelected={handleSelected}
          />
        ))}
      </CountryGrid>
    </Container>
  );
};

export default CountryList;

๋™์ผํ•œ ๋‚˜๋ผ๊ฐ€ selectedCountries์— ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ , ํ•ด๋‹น ๋‚˜๋ผ๊ฐ€ ์žˆ์œผ๋ฉด ์ œ๊ฑฐํ•˜๊ณ  ์—†์œผ๋ฉด ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ์Œ

CountryCard.tsx

import React from "react";
import { Country } from "../types/Country";
import styled from "styled-components";

const CountryItem = styled.li`
  flex: 0 0 33.3333%;
  box-sizing: border-box;
  padding: 10px;
  text-align: center;
  border: 1px solid black;

  h3 {
    margin: 10px 0;
  }

  p {
    margin: 5px 0;
  }

  img {
    width: 100px;
    height: auto;
  }
`;

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

const CountryCard: React.FC<CountryCardProps> = ({ country, handleSelected }) => {
  return (
    <CountryItem onClick={() => handleSelected(country)}>
      <h3>{country.name.common}</h3>
      <p>{country.region}</p>
      <img src={country.flags.png} alt={`${country.name.common} flag`} />
    </CountryItem>
  );
};

export default CountryCard;

๋‚˜๋ผ ๋ชฉ๋ก์—์„œ ์›ํ•˜๋Š” ๋‚˜๋ผ๋ฅผ ํด๋ฆญํ•˜๋ฉด ์ข‹์•„ํ•˜๋Š” ๋‚˜๋ผ ํ•ญ๋ชฉ์œผ๋กœ ์ด๋™ํ•˜๊ฒŒ๋” ๊ตฌํ˜„์€ ์„ฑ๊ณตํ–ˆ๋Š”๋ฐ ์•„๋ฌด๋ฆฌ ๋งŽ์€ ๋‚˜๋ผ๋ฅผ ํด๋ฆญํ•ด๋„ ํ•œ ๋‚˜๋ผ๋งŒ ์ด๋™ํ–ˆ๋‹ค ์‚ฌ๋ผ์กŒ๋‹ค

์•„๋ฌด๋ž˜๋„ handleSelected ํ•จ์ˆ˜์— ๊ฒฐํ•จ์ด ์žˆ๋Š” ๊ฑฐ ๊ฐ™์•„ ์ˆ˜์ •์„ ํ•ด์คฌ๋‹ค

์ „

const handleSelected = (country: Country): void => {
    if (
      !selectedCountries.find(
        (country: Country) => country.name.common === country.name.common
      )
    ) {
      setSelectedCountries([...selectedCountries, country]);
    } else {
      setSelectedCountries(
        selectedCountries.filter((country: Country) => {
          return country.name.common !== country.name.common;
        })
      );
    }
  };

ํ›„

const handleSelected = (country: Country): void => {
    if (
      !selectedCountries.find(
        (selectedCountry: Country) => selectedCountry.name.common === country.name.common
      )
    ) {
      setSelectedCountries([...selectedCountries, country]);
    } else {
      setSelectedCountries(
        selectedCountries.filter((selectedCountry: Country) => {
          return selectedCountry.name.common !== country.name.common;
        })
      );
    }
  };

์ผ๋‹จ find, filter๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ๋ฐ›๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜ ์ด๋ฆ„์„ ๋ฐ”๊ฟ”์ฃผ์—ˆ๋‹ค. ์™œ๋ƒ๋ฉด ์œ„์— handleSelected์˜ ๋งค๊ฐœ๋ณ€์ˆ˜ ์ด๋ฆ„์ด๋ž‘ ๊ฐ™๊ธฐ์— ์˜ค์ž‘๋™์„ ์ผ์œผํ‚ค๋Š” ๋“ฏ ์‹ถ์—ˆ๋‹ค.

์ˆ˜์ •์„ ํ•ด์ฃผ๊ณ  ๋‚˜๋‹ˆ ์ •์ƒ์ ์œผ๋กœ ์ž˜ ์ž‘๋™๋˜๋Š” ๊ฑธ ๋ณผ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

๋งˆ์น˜ js, react๋ฅผ ์ฒ˜์Œ ๋ฐฐ์šธ ๋•Œ ๋ฌด์ฒ™์ด๋‚˜ ์–ด๋ ค์›Œํ–ˆ๋˜ ๊ฒƒ์ฒ˜๋Ÿผ
typescript๋„ ํ—ท๊ฐˆ๋ฆฌ๋Š” ๋ถ€๋ถ„์ด ๋„ˆ๋ฌด ๋งŽ๋‹ค..ใ… ใ… ใ… 

profile
๊ฐœ๋ฐœ์ž๊ฐ€ ๋˜๊ณ  ์‹ถ์–ด~~~

2๊ฐœ์˜ ๋Œ“๊ธ€

ํ•ญ์ƒ ์ž˜ ๋ณด๊ณ  ๊ฐ‘๋‹ˆ๋‹ค~!

1๊ฐœ์˜ ๋‹ต๊ธ€