SetUp
npx create-react-app 프로젝트_이름 --template typescript
Styled-Components 설치
npm i styled-components
npm i --save-dev @types/styled-componentsReactRouter 설치
npm I react-router-dom@5.3.0
npm i --save-dev @types/react-router-dom
ReactRouter
에서 제공하는 기능으로, 동적으로 변하는 url의 주소의 정보를 뽑아오는데 사용된다.
url 주소가 /users/:coinId 라면 :coinId 동적으로 변하는 사용자의 고유 ID값.
useParams
을 사용하면 이 부분을 쉽게 가져올 수 있다.
Router.tsx
<Route path="/:coinId">
<Coin />
</Route>
Coin.tsx
interface RouteParams {
coinId: string;
}
function Coin() {
const { coinId } = useParams<RouteParams>();
return <h1>Coin: {coinId}</h1>;
}
Reset Css
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
Google Font
https://fonts.google.com/
Coins.tsx파일을 만든다.
Coin API주소
https://api.coinpaprika.com/v1/coins
그리고 styled-component을 활용해서 화면을 구성한다
import { Link } from "react-router-dom";
import styled from "styled-components";
const Container = styled.div`
padding: 0px 20px;
// 화면의 크기에 따라서 중앙에 배치시키기 위해서 추가.
max-width: 480px;
margin: 0 auto;
`;
const Header = styled.header`
height: 15vh;
display: flex;
justify-content: center;
align-items: center;
`;
const CoinList = styled.ul``;
const Coin = styled.li`
background-color: white;
margin-bottom: 10px;
border-radius: 15px;
color: ${(props) => props.theme.bgColor};
a{
transition: color 0.2s ease-in;
display: block;
padding: 20px;
/* 이 부분에 padding값+block속성을 줘서 한줄을 <Link>태그로 클릭할 수 있게 만들어줌 */
}
&:hover{
a{
color: ${(props) => props.theme.accentColor};
}
}
`;
const Title = styled.h1`
color: ${(props) => props.theme.accentColor};
font-size: 48px;
`;
const coins = [
{
id: "btc-bitcoin",
name: "Bitcoin",
symbol: "BTC",
rank: 1,
is_new: false,
is_active: true,
type: "coin",
},
];
function Coins() {
return (
<Container>
<Header>
<Title>Coins</Title>
</Header>
<CoinList>
{coins.map((coin) => (
<Coin key={coin.id}>
<Link to={`/${coin.id}`}>{coin.name} →</Link>
</Coin>
))}
</CoinList>
</Container>
);
}
export default Coins;
클릭했을 때 이동하는 부분은 a태그가 아닌 react-router의 Link
태그를 이용해서 처리한다.
a태그를 이용하면 새로고침이 발생하기 때문이다.
이제 받아오는 API값의 형태를 TypeScript에게 가르쳐줘야 한다.
interface CoinInterface{
id: string,
name: string,
symbol: string,
rank: number,
is_new: boolean,
is_active: boolean,
type: string,
}
const[coins, setCoins] = useState<CoinInterface[]>([]);
: coins는 CoinInterface의 타입을 가진다.
다음과 같이 useEffect
을 사용해서 API을 처리한다.
response
을 받기 위해서 await을 사용한다.
API을 다 받아오기 전에는, <Loader>
컴포넌트가 나온다.
React Query
로 바꿀 것이다. useEffect(() => {
(async() =>{
const response = await fetch("https://api.coinpaprika.com/v1/coins");
const json = await response.json();
setCoins(json.slice(0,10));
setLoading(false);
})()
},[])
{loading ? (
<Loader>"Loading..."</Loader>
):(
<CoinList>
{coins.map((coin) => (
<Coin key={coin.id}>
<Link to={`/${coin.id}`}>{coin.name} →</Link>
</Coin>
))}
</CoinList>
)}
코인 Symbol Api
https://coinicons-api.vercel.app/api/icon/${coin.symbol.toLowerCase()
각 코인화면 만들기
비하인더씬이란? Link태그로
: 보이지 않는 방식으로 데이터를 보내는 방식
//Coins.tsx -> Coin.tsx로 보낸다.
<Link
to={{
pathname: `/${coin.id}`,
state: {name: coin.name}
}}>
{coin.name} →
</Link>
Coin.tsx
interface RouteState{
name: string;
}
const {state} = useLocation<RouteState>();
// 비하인드씬으로 보낸 데이터를 받아온다.
function Coin(){
<Title>{state.name}</Title>
}
Coin Data를 받기 위한 URL
https://api.coinpaprika.com/v1/coins/btc-bitcoin
https://api.coinpaprika.com/v1/tickers/btc-bitcoin
지금은 useEffect와 async await을 이용하여 fetch하는데, 나중에는 ReactQuery
로 바꿀 것이다.
useEffect(()=>{
(async() =>{
const infoData = await(
await fetch(`https://api.coinpaprika.com/v1/coins/${coinId}/`)
).json();
console.log(infoData); // → temp1
const priceData = await(
await fetch(`https://api.coinpaprika.com/v1/tickers/${coinId}/`)
).json();
console.log(priceData); // → temp2
})();
}, [])
console.log해서 데이터를 뽑은 다음 console창에서 Store object as global variable
로 저장 : temp1
Object.keys(temp1).join();
: 나오는 결과값 그대로 Interface InfoData로 복사
Shift+Option+i
: 선택한 모든 문자열에 가장 우측 끝으로 포커싱
타입의 value
얻어오기
Object.values(temp1).map(v=> typeof v).join()
만약 가져온 값중에서 Object가 있다면 그것이 어떤 Object인지 확인해서 Typescript에게 가르쳐줘야한다.
interface Test{
tags: Object
}
// ↓
interface ITag{
coin_counter: number;
ico_counter: number;
id: string;
name: string
}
interface Test{
tags: ITag[];
}
: route안에 있는 또 다른 route이다.
프로젝트 안에는 Router.tsx라는 파일이 있다.
그래서 Coin.tsx안에서 쓰는 Route가 결국 Nested Router
가 되는 것이다.
Router.tsx
function Router() {
return (
<BrowserRouter>
<Switch>
<Route path="/:coinId">
<Coin />
</Route>
<Route path="/">
<Coins />
</Route>
</Switch>
</BrowserRouter>
);
}
Coin.tsx
interface RouteParams{
coinId: string;
}
function Coin() {
const { coinId } = useParams<RouteParams>();
...
return (
...
<Switch>
<Route path={`/:coinId/price`}>
<Price></Price>
</Route>
<Route path={`/:coinId/chart`}>
<Chart></Chart>
</Route>
</Switch>
</>
)}
</Container>
);
}
: 현재 URL과 라우터에 정의된 경로를 비교하요 일치하는지 확인하는데 사용한다.
priceMatch,chartMatch을 사용하여 현재 URL과 일치하는 지 확인하고, isActive prop을 이용하여,
사용자가 클릭한 탭의 색을 accentColor로 적용해준다.
const Tab = styled.span<{isActive: boolean}>`
text-align: center;
text-transform: uppercase;
font-size: 12px;
font-weight: 400;
background-color: rgba(0, 0, 0, 0.5);
padding: 7px 0px;
border-radius: 10px;
color: ${(props)=> props.isActive ? props.theme.accentColor : props.theme.textColor};
a {
display: block;
}
`;
const priceMatch = useRouteMatch('/:coinId/price');
const chartMatch = useRouteMatch('/:coinId/chart');
<Tabs>
<Tab isActive={chartMatch !== null}>
<Link to={`/${coinId}/chart`}>Chart</Link>
</Tab>
<Tab isActive={priceMatch !== null}>
<Link to={`/${coinId}/price`}>Price</Link>
</Tab>
</Tabs>
이런 유용한 정보를 나눠주셔서 감사합니다.