React JS 마스터클래스 (3. CRYPTO TRACKER -1 )

짜스의 하루 ·2024년 6월 2일

useParam

(강의 진행을 따라가기 위해 react-router-dom 버전을 5.XX로 진행하겠다)

useParams는 React Router에서 제공하는 훅 중 하나이다. 이 훅은 현재 URL의 경로 매개변수를 가져오는 데 사용된다.
예를 들어, /:coinId와 같은 경로에서 coinId와 매개변수가 된다.

위와 같이 /:coinId 로 정의해두고,

interface RouteParams { coinId: string; } :
TypeScript를 사용하여 RouteParams라는 인터페이스를 정의한다. 이 인터페이스는 coinId라는 문자열 타입의 속성을 가지고 있다.

const { coinId } = useParams<RouteParams>() :
useParams 훅을 호출하여 URL에서 경로 매개변수를 추출한다.
이 코드에서는 RouteParams 인터페이스를 제네릭으로 전달하여 매개변수의 타입을 지정한다.
이렇게 함으로써 TypeScript에서 추출된 매개변수의 타입을 검사할 수 있다.

return <h1>Coin: {coinId}</h1> :
추출된 coinId를 사용하여 화면에 "Coin: "과 해당 코인의 ID를 표시하는 <h1> 요소를 반환한다.

간단하게 화면에서 test를 해보자면

useParams 훅을 사용하여 URL에서 추출한 coinId를 화면에 표시하는 것을 확인할 수 있다.


Theme

테마를 작성하는 이유 ? 를 곰곰히 생각해 보았다.
--> 테마 파일을 따로 작성하면 서로 다른 컴포넌트에서 동일한 테마를 사용할 수 있다. 이를 통해 애플리케이션 전반에서 일관된 디자인을 유지가 가능해진다.
--> styled-components를 사용하여 컴포넌트별로 테마를 적용할 수 있다. 이 경우 ThemeProvider를 사용하여 전역적으로 테마를 설정하고, 각 컴포넌트에서 props.theme을 사용하여 테마에 맞는 스타일을 적용할 수 있다.

만약, Theme.ts 파일을 이렇게 정의해 두었다.


Home part

먼저,Home 화면에 대해서 꾸며줄 예정이다.
이제는 styled-components에 익숙해져야 할 것이다!

우선 화면에 보일 태그들은 이렇게 생겼다고 생각하면 될 것 같다.
이제 이것들을 css 효과를 적용을 시켜보자면,

const Container = styled.div`
  padding: 0px 10px;
  max-width: 480px;
  margin: 0 auto;
`;

const Header = styled.header`
  height: 10vh;
  display: flex;
  justify-content: center;
  align-items: center;
`;

const CoinList = styled.ul``;
const Coin = styled.li`
  background-color: white;
  color: ${(props) => props.theme.bgColor};
  margin-bottom: 10px;
  border-radius: 15px;
  padding: 20px;

  a {
    transition: color 0.2s ease-in-out;
    display: block;
  }

  &:hover {
    a {
      color: ${(props) => props.theme.accentColor};
    }
  }
`;

const Title = styled.h1`
  font-size: 48px;
  color: ${(props) => props.theme.accentColor};
`;

const Loader = styled.span`
  text-align: center;
  display: block;
`;

요렇게 정의할 수 있다.
우선 styled-components를 적용해서 좋아진 점 : 태그 명이 확실해졌고, 태그 안에 css를 정의해 두니, 보기에도, 이해하기에도 훨씬 간편한 것 같다.

자, 이제 fetch를 사용해서 api를 받아올 것이다. (이후에 react query를 사용할 것이지만, 먼저 왜 리액트 쿼리가 필요한지, 알아야 하기 때문에!)

api를 받아오면, 그 정보들이 어떤 정보인지, 어떤 타입인지를 알려줘야 한다. 아니면 타입스크립트에서 화를 내면서 오류를 발생한다.
--> 이는 interface를 사용하면 된다.

interface CoinInterface {
  id: string;
  name: string;
  symbol: string;
  rank: number;
  is_new: boolean;
  is_active: boolean;
  type: string;
}

api를 먼저 살펴보고, 데이터를 통해서 어떤걸 받아올 건지 살펴보고, 위와 같이 interface를 통해 각각 어떤 타입인지 타입스크립트에게 알려주어야 한다.


배열로 받아와야 하기 때문에, <CoinInterface[]> 형태로 넘겨주어야 한다.

이후 useEffect() 를 사용해서 fetch를 받아와햐 한다.
: useEffect를 사용하면 컴포넌트가 마운트될 때(즉, 처음 렌더링될 때) 한 번만 특정 작업을 수행하도록 할 수 있다. --> useEffect의 의존성 배열에 빈 배열 []을 전달하면, 해당 효과는 컴포넌트가 마운트될 때 한 번만 실행된다.


이후 coins를 map으로 받아와 {coin.name}으로 코인의 이름을 화면에 뿌려주고,
<Link to={`/${coin.id}`}> 를 사용해서 coin을 눌렀을 때, 해당 코인의 id로 링크 이동을 할 수 있도록 제공해 주었다.



이전과 달라진 부분을 찾아볼 수 있다.

첫번째, Link 컴포넌트에서 Path이외에 state를 전달하고 있다.
react-router-dom 의 Link 컴포넌트를 사용하여 to 속성에 pathname과 state를 정의하면, useLocation 훅을 사용해서 해당 경로로 이동할 때 전달된 state를 접근이 가능하다.

해당 코인을 누르면, 해당 경로로 이동하면서 state로 coin.name이 전달된다.

이후 해당 경로로 이동한 컴포넌트에서 useLocation을 사용하면, location.state에 접근할 수 있으며, 여기서 coin.name을 확인할 수 있.

--> 즉 현재, <Coin/> 컴포넌트에 Link를 보내고 있으므로, <Coin/>컴포넌트에서 state를 사용할 수 있다는 점이다.

Coin 컴포넌트를 살펴보면, Link에서 보낸 state를 받아서 사용하고 있다.
Coin 컴포넌트에서 useLocation 훅을 사용하여 Link에서 전달한 state에 접근할 수 있다.
(import { useLocation } from 'react-router-dom';) 로 useLocation을 받아와야 한다.

현재 코드에서는 state 객체에서 name 속성을 받아와서 제목으로 표시하고 있다. 만약 state가 존재하지 않으면 'Loading...' 텍스트를 대신 표시하도록 코드를 작성해 둔 것이다.

여기서 중요한 점은
const { state } = useLocation ()을 처음 작성했을 때, state에 대한 명시가 되어 있지 않는다는 타입스크립트의 오류를 확인할 수 있었다.

이에 interface를 정의해 state에 대해 정의를 해두었다.

interface RouteState {
  name: string;
}

name : string 임을 타입스크립트에게 알려주고, useLocation<RouteState>() 로 정해주었다.


Coin에 정보 뿌리기

우리는 두가지 정보를 Coin에 뿌려줄 것이다.
첫 번째 : Coin에 대한 정보
두 번째 : Coin의 가격에 대한 정보

두가지를 뿌려줄 예정이다.

아직 react query를 사용하기 전이니 이전과 동일하게 fetch를 사용해서 api를 가져와보자

궁금한 점 --> 여기서 왜 async, await를 사용하는 이유는 ?
비동기 작업 수행: async 함수 내부에서 fetch 함수를 호출하고 있다.
fetch 함수는 네트워크 요청을 보내고 해당 응답을 받아오는 작업이다. 이러한 네트워크 요청은 시간이 오래 걸릴 수 있으므로 비동기 방식으로 처리해야 한다.

프라미스 처리: await 키워드를 사용하여 fetch 함수의 결과를 기다리고 있다. await는 fetch 함수가 반환하는 프라미스를 기다렸다가 처리된 데이터를 반환하기 때문이다.

infoData, priceData를 받아온 뒤, 받아온 데이터를 setXXX()로 변경을 해주어야 한다
--> useState()로 정의해주자

const [info, setInfo] = useState({});
const [priceInfo, setPriceInfo] = useState({});

코인의 정보를 나타낸 info와 가격의 정보를 나타낸 priceInfo를 useState({})로 (빈 배열 --> 정보를 배열 형태로 받아오기 때문) 지정해 두었다

잠깐!!
이렇게 지정만 해두면 타입스크립트는 info(), priceInfo()가 빈 배열인 줄만 알고 있기 때문에 interface로 지정을 해주어야 한다.

interface InfoData {
  id: string;
  name: string;
  symbol: string;
  rank: number;
  is_new: boolean;
  is_active: boolean;
  type: string;
  description: string;
  message: string;
  ...
}

interface PriceData {
  id: string;
  name: string;
  symbol: string;
  rank: number;
  circulating_supply: number;
  total_supply: number;
  max_supply: number;
  beta_value: number;
  first_data_at: string;
  last_updated: string;
  quotes: {
    USD: {
      ath_date: string;
      ath_price: number;
      market_cap: number;
      market_cap_change_24h: number;
      percent_change_1h: number;
      percent_change_1y: number;
      percent_change_6h: number;
      percent_change_7d: number;
      ...
    };
  };
}

사실 엄청 많은데 대충 이정도라고 생각하고, interface에 타입을 정리해두었으면,

const [info, setInfo] = useState<InfoData>();
const [priceInfo, setPriceInfo] = useState<PriceData>();

이렇게 알려주어야 한다.
이제, 각각 <InfoData>, <PriceData>로 어떤 형태인지 (배열인지) 알기 때문에 괄호 안에 {}는 없애주어도 된다!

profile
2024. 01. 02 ~ 백앤드 공부 시작, 2024. 04.01 ~ 프론트 공부 시작

0개의 댓글