우선, Routes를 적용하기 전에
Coin 컴포넌트 화면을 꾸며주도록 하겠다.

우리가 api를 받아왔던 정보들을 이용하면 간단하게 만들 수 있다.
우선 위의 그림과 같이 <Overview/>, <OverviewItem/>, <Description/> 을 지정해 줄 예정이다!
const Overview = styled.div`
display: flex;
justify-content: space-between;
background-color: rgba(0, 0, 0, 0.5);
padding: 10px, 20px;
border-radius: 10px;
`;
const OverviewItem = styled.div`
display: flex;
flex-direction: column;
align-items: center;
span:first-child {
font-size: 10px;
font-weight: 400;
text-transform: uppercase;
margin-bottom: 5px;
}
`;
const Description = styled.p`
margin: 20px 0px;
`;
먼저 이와 같이 css 속성을 지정해주고,
여기서 span은 OverviewItem에 들어갈 'Rank : ' 와 같은 내용을 나타낸다.

이후 이런 식으로 화면 구성을 해주었다.
쉽게 찾아볼 수 있는 부분은
{info?.rank}와 같은 부분인데, info?가 있을 수도 있고, 없을 수도 있기 때문에 ?로 명시를 해주어서 오류가 발생하지 않도록! 해준 것이다.
확실히 interface에 정의를 해놓으니까, 자동완성 도움도 받아서 빠르게 화면에 뿌려줄 내용을 정해서 가져올 수 있는 것 같다!
이제, Price와 Chart를 보여줄 시간이다.

화면에 각각 price, chart를 보여줄 예정인데,
여기서 중요한 부분은 저 price를 눌렀을 때,
http://localhost:3000/bnb-binance-coin/price
로 기존 url에서 price를 타고 들어간다는 것이다.
그럼 먼저 Price, Chart의 컴포넌트를 각각 만들어보자

React Router를 사용하여 Coin 컴포넌트 내에서 Price 컴포넌트를 불러오기 위해서는 Route를 사용해야 한다.
--> 이렇게 하면 사용자가 특정 링크를 클릭했을 때 해당 경로로 라우팅되어 Price 컴포넌트가 렌더링된다. Switch 컴포넌트 내에 여러 Route를 정의하여 각 경로에 따라 다른 컴포넌트를 렌더링할 수 있다.
<Switch>
<Route path={`/${coinId}/price`}>
<Price />
</Route>
<Route path={`/${coinId}/chart`}>
<Chart />
</Route>
</Switch>
이렇게 정의하면, 해당 코인 링크로 들어가/price를 추가적으로 입력하면, 화면에 아래와 같이 price가 나타나는 것을 확인할 수 있다. 
이제 Route로 컴포넌트를 불러왔으니,
클릭할 수 있는 요소(?)를 만들어야 한다.
탭을 생성해서 price를 누르면, price 컴포넌트가 렌더링이 되고, chart를 누르면 chart가 렌더링 되도록 해야 한다.
이 과정을 거치기 위해서 --> Link를 사용해야 한다.
Link: 사용자가 클릭할 수 있는 링크를 렌더링한다. 이 링크를 클릭하면 브라우저의 URL이 변경되고 해당 URL에 매핑된 Route가 활성화된다.
Link는 HTML의 <a> 태그와 유사하게 작동하지만, 페이지 전체를 새로고침하지 않고 애플리케이션의 상태만 변경하게 된다

Link to={/${coinId}/price}>Price</Link>를 클릭하면 브라우저의 URL이 /${coinId}/price로 변경된다.
Link는 페이지를 다시 로드하지 않고 브라우저의 주소를 변경하여 React Router의 라우터를 통해 페이지를 탐색하게 된다.
Route는 URL이 특정 경로와 일치할 때 렌더링할 컴포넌트를 정의한다. URL이 /:coinId/price와 일치하면 <Price /> 컴포넌트가 렌더링되게 된다.
즉, Link로 생성된 요소를 클릭하면, URL이 /${coinId}/price로 변경되고, 변경 후, <Price/> 컴포넌트가 렌더링이 되는 것이다.
--> React Router는 페이지 전체를 새로고침하지 않고도 URL 변경에 따라 적절한 컴포넌트를 렌더링하여 SPA(Single Page Application) 특성을 유지할 수 있게 된다.
이제 <Tabs>, <Tab> 컴포넌트를 스타일을 줘보도록 하겠다. 
useRouteMatch : 현재 URL이 특정 경로와 일치하는지 확인하고, 일치하는 경우 그 경로에 대한 매칭 정보를 반환한다.
useRouteMatch는 각각 priceMatch, chartMatch 객체를 반환하며, 이 객체는 URL이 경로와 일치할 때 정보를 포함하고, 일치하지 않으면 null을 반환하게 된다.
const priceMatch = useRouteMatch('/:coinId/price');
const chartMatch = useRouteMatch('/:coinId/chart');
useRouteMatch('/:coinId/price')와 useRouteMatch('/:coinId/chart')는 각각 현재 URL이 /:coinId/price와 /:coinId/chart 경로와 일치하는지 확인한다.
priceMatch와 chartMatch 변수는 useRouteMatch 훅이 반환하는 값을 받는다. URL이 해당 경로와 일치하면 match 객체를 반환하고, 일치하지 않으면 null을 반환

priceMatch !== null과 chartMatch !== null을 통해 현재 URL이 /price 또는 /chart 경로와 일치하는지 확인한다.
즉, priceMatch가 null이 아닐 때--> 일치할 때를 나타낸다.
이 결과를 Tab 컴포넌트의 isActive prop에 전달하여 해당 탭이 활성화된 상태인지 결정하게 된다.

isActive prop을 받아서 해당 탭이 활성화된 상태인지 여부에 따라 스타일을 변경한다.
isActive가 true인 탭은 텍스트 색상이 강조된 색(props.theme.accentColor)으로 표시되고, false인 탭은 텍스트가 기본 textColor로 표시되게 된다.
여기서 궁금한 점!
const Tab = styled.span<{ isActive : boolean}>
styled.span에 제네릭 타입 <{ isActive: boolean }>를 전달하여 isActive라는 boolean 타입의 prop을 정의한다.
요약
isActive Prop 정의 : Tab 컴포넌트에 isActive라는 boolean 타입의 prop을 정의한다.
경로 일치 여부 확인 : useRouteMatch를 사용하여 현재 URL이 특정 경로와 일치하는지 확인한다.
isActive Prop 설정 : useRouteMatch의 결과에 따라 isActive prop에 true 또는 false 값을 넘긴다.
스타일 변경 : isActive 값에 따라 Tab 컴포넌트의 색상이 동적으로 변경된다.
useRouteMatch를 사용하는 이유
(왜 사용할까? 사용하지 않아도 Link로 화면 렌더링하고, 그 정보로 Route를 통해서 필요한 컴포넌트를 렌더링하면 되는거 아닌가?!)
동적인 스타일링 : isActive prop을 도입하여 현재 활성화된 탭을 시각적으로 구별하는 것이 가능하다. 이것은 사용자에게 현재 어떤 탭이 활성화되어 있는지 명확하게 보여주게 된다.
코드의 가독성 : useRouteMatch를 사용하면 해당 경로와 현재 URL이 일치하는지 여부를 간단하게 확인할 수 있다. 이렇게 하면 코드를 읽는 사람이 빠르게 이해할 수 있다.
동적 렌더링 : useRouteMatch를 사용하여 URL에 따라 특정 컴포넌트를 렌더링하거나 다른 작업을 수행할 수 있다. 이는 웹 애플리케이션이 동적으로 변하는 URL에 대응할 수 있도록 도와주게 된다.
라우팅 관련 로직 분리 : useRouteMatch를 사용하여 라우팅 관련 로직을 별도로 분리할 수 있다. 이것은 코드의 유지보수성과 재사용성을 향상시킨다.
이러한 장점이 있다고 한다!
React Query : 데이터를 관리하고 처리하기 위한 라이브러리. 주로 API 호출 결과를 캐시하고 관리하여 데이터를 효율적으로 처리하는 데 사용된다.
지금까지 (React Query)를 사용하기 전에는
const [coins, setCoins] = useState<ICoin[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
(async () => {
const response = await fetch('https://api.coinpaprika.com/v1/coins');
const json = await response.json();
setCoins(json.slice(0, 100));
setLoading(false);
})();
}, []);
이런식으로 useEffect()를 사용해서 fetch를 통해서 ~~ api 결과를 ~~ json()타입으로 변경해서 엄청 길게 가져왔었다. 여기서 loading을 setState를 사용해서 관리하고 적용했었다.
이제는 react query를 사용해보자
먼저 fetch 함수를 정의할 수 있도록 (컴포넌트랑 떨어져서 만드는게 좋다) FetchCoins 함수를 생성해보자

FetchCoins 함수는 fetch 함수를 사용하여 CoinPaprika API의 엔드포인트인 'https://api.coinpaprika.com/v1/coins'에 GET 요청을 보낸다.
이후 then() 메서드를 사용하여 응답(response) 객체를 받고, .json() 메서드를 사용하여 JSON 형식으로 응답 데이터를 추출한다.
자, FetchCoins로 데이터를 가져왔으니, 사용해보러 가보자

useQuery 훅을 사용하여 CoinPaprika API로부터 암호화폐 정보를 가져온다.useQuery 훅은 첫 번째 매개변수로 쿼리 키를, 두 번째 매개변수로 데이터를 가져오는 함수를 받는다.--> FetchCoins 함수에서 받아온 JSON 데이터를 ICoin 인터페이스를 적용하여 타입을 지정한다.
이렇게 하면 TypeScript는 데이터가 ICoin 인터페이스와 일치하는지 확인하고, 일치하지 않는 경우 경고를 표시하게 된다.

이전에 useState로 사용했던 값들을 제거하고 useQuery에서 반환 받은 isLoading, data를 이용하면 된다.
이렇게 React Query 를 사용하면 깔끔하게 코드를 작성할 수 있게 된다.
이제 Coin component를 React Query로 변경해보자
그 전애!React Query 개발자 도구를 사용해보자
--> 이 도구를 통해 쿼리 캐시 상태, 최근 쿼리, 오류, 재시도 등을 실시간으로 모니터링할 수 있다.
import { ReactQueryDevtools } from 'react-query/devtools';
const App = () => {
return (
<>
<GlobalStyle />
<Router />
<ReactQueryDevtools initialIsOpen={true} />
</>
);
};
export default App;
ReactQueryDevtools를 import 한 후, ReactQueryDevtools를 JSX 코드에 추가하고 initialIsOpen를 true로 설정하고,
index.tsx 파일에 넘어가서

QueryClient 생성 : QueryClient를 생성하여 React Query의 클라이언트를 설정한다.
QueryClientProvider 사용: QueryClientProvider로 애플리케이션을 감싸고, client 속성으로 queryClient를 전달한다.
이렇게 하면 애플리케이션에서 React Query 개발자 도구를 사용할 수 있게 되며, 이 도구를 통해 쿼리 캐시 상태, 최근 쿼리, 오류, 재시도 등을 실시간으로 모니터링할 수 있다.
요러한 개발자 도구를 확인할 수 있다.
자, 이제 다시 <Coin/> 컴포넌트를 React Query로 변경해보도록 하겠다.
Api.tsx 파일에는 fetch 함수를 정의해 놓는 곳이다.
이렇게 공통으로 들어갈 주소는 BASE_URL로 정의하고, 가져다 쓰면 더 깔끔해진다.
이렇게 Info Fetch와 Tickers Fetch 함수를 통해 api를 받아온 뒤,
<Coin/> 컴포넌트에서 useQuery를 이용해서 api를 사용해 보겠다.
const { isLoading: infoLoading, data: infoData } = useQuery<InfoData>(
['info', coinId],
() => FetchCoinsInfo(coinId)
);
const { isLoading: tickersLoading, data: tickersData } = useQuery<PriceData>(
['tickers', coinId],
() => FetchCoinsTickers(coinId)
);
useQuery<InfoData> 는 InfoData 타입의 데이터를 가져오는 쿼리를 실행한다.
['info', coinId]는 쿼리 키 --> 이 쿼리 키는 React Query의 캐싱 메커니즘에서 고유하게 식별되는 쿼리를 나타낸다. .() => FetchCoinsInfo(coinId)는 데이터를 가져오는 함수이다. 여기서는 coinId를 인자로 받아 FetchCoinsInfo 함수를 호출한다.infoLoading 은 데이터가 로딩 중인지 여부를 나타내는 Boolean 값을 infoLoading 변수에 할당한다.data: infoData는 쿼리의 결과 데이터를 infoData 변수에 할당한다.두번째 useQuery<tickersData> 역시 똑같이 이루어진다.
useQuery를 두 번 사용하면서 각 쿼리를 고유하게 식별하고 관리해야 한다.
고유한 쿼리 키 사용:
useQuery 훅을 사용할 때, 쿼리 키를 고유하게 만들어야 한다. .
[ 'info', coinId ]와 [ 'tickers', coinId ]는 각각 고유한 쿼리 키로 사용되고 있다.
--> 이렇게 함으로써 React Query는 두 쿼리를 별도로 캐싱하고 관리할 수 있다.
로딩 상태 변수의 고유 이름 지정:
isLoading 변수의 이름이 겹치지 않도록 infoLoading과 tickersLoading으로 각각 지정한다.
useQuery 훅이 반환하는 로딩 상태와 데이터를 구분하여 처리하기 위해 변수명을 명확하게 지정해야 한다.
loading 변수는 infoLoading과 tickersLoading 중 하나라도 true인 경우 true가 된다.
즉, 두 개의 쿼리 중 하나라도 로딩 중이면 loading 변수는 true로 설정된다.
이후 실행해보면,

이전과 같이 제대로 작동하는 것을 확인할 수 있다.
또한, query 키 또한 설정한대로 각각 나타나는 것 또한 확인할 수 있다.
React Query는 데이터 패칭을 최적화하고, 네트워크 요청을 최소화하기 위해 캐싱 메커니즘을 사용한다. 이에 따라 한 번 로딩된 데이터는 캐시에 저장되고, 동일한 쿼리를 다시 요청할 때 캐시된 데이터를 반환한다.
--> 이 의미는 Bitcoin 페이지를 한번 로딩하면 그때는 'Loading...' 문구가 뜨고, 필요한 데이터를 가져온다. 그리고 그 필요한 데이터를 캐시에 저장해 둔다.
이후 다시 Bitcoin 페이지를 로딩하면, 'Loading..' 문구는 더이상 나오지 않는다.
캐시에 데이터가 저장되어 있기 때문이다!
이제 react query에 대해서도 배웠겠다
Chart를 변신시켜보도록 하겠다.
우선, Chart에 뿌릴 데이터 api에 대해서 받아보도록 하겠다.
export function FetchCoinHistory(coinId: string) {
const endDate = Math.floor(Date.now() / 1000);
const startDate = endDate - 60 * 60 * 24 * 7 * 2;
return fetch(
`${BASE_URL}/coins/${coinId}/ohlcv/historical?start=${startDate}&end=${endDate}`
).then((response) => response.json());
}
const endDate = Math.floor(Date.now() / 1000); : endDate는 현재 시간의 유닉스 타임스탬프를 계산한다. Date.now() 함수는 현재 시간을 밀리초 단위로 반환하고, 이를 1000으로 나누어 초 단위의 타임스탬프로 변환한 뒤 소수점 이하를 버린다(반내림)const startDate = endDate - 60 * 60 * 24 * 7 * 2; : startDate는 endDate에서 14일(7일 * 2)을 뺀 값을 계산한다.return fetch(${BASE_URL}/coins/${coinId}/ohlcv/historical?start=${startDate}&end=${endDate}) : fetch 함수를 사용하여 CoinPaprika API의 엔드포인트로 요청을 보낸다.ohlcv/historical 엔드포인트는 OHLCV 데이터를 가져오는 데 사용된다. 요청의 쿼리 파라미터로 시작 날짜(start)와 종료 날짜(end)를 전달한다. 
이후 <Chart /> 컴포넌트에서 이를 받아온다.
const { isLoading, data} = useQuery(['ohlve', coinId],() => FetchCoinHistory(coinId)... : useQuery의 고유한 키 값을 키 : ohlve, 값 : coinId로 받아오고, api fetch 함수인 FetchCoinHistory는 coinId를 받아야 하기 때문에, 익명함수로 coinId를 넘겨주었다.
아 맞다, 저기 Chart = ( {coinId} : chartProps) 부분은
--> <Chart coinId={coinId} />
Coin 컴포넌트에서 컴포넌트에 props를 보내서 받아온 것!
Chart 입장에서는 coinId에 대해서 알지 못하기 때문에 chartProps에 정의해서 타입스크립트에게 알려준 것이다.
.