React Query 패키지는 편리한 방식으로 데이터를 fetch 시킬 수 있다.
리액트 쿼리를 배우기 전에 우리가 아는 방식으로 데이터를 fetch 시켜보자.
✔️react-query 설치
npm i react-query
path="/:coinId"
는 Router에게 URL이 변수값을 갖는다는 걸 말해주는 방식!
<Route path="/:coinId" element={<Coin />} />
✔️CSS Reset
createGlobalStyle
: 한 컴포넌트를 만들게 해주는데, 렌더링 될 때 그 컴포넌트는 전역 스코프에 스타일들을 올려준다.
https://github.com/zacanger/styled-reset/blob/master/src/index.ts
<></> (Fragment)
: 일종의 유령 컴포넌트, 부모가 없이 서로 붙어있는 많은 것들을 리턴하게 해준다.
✔️폰트 설치
https://fonts.google.com/
coinpaprika api 이용해서 실습
https://api.coinpaprika.com/v1/coins
Coins에서 각 코인들을 누르면 세부 페이지로 이동하게 할건데, <a href="">
을 쓰면 페이지가 새로고침되어 버리기 때문에 link를 이용하지 않을 것이다. 대신에 react router dom을 통해 link component를 사용!
API로부터 데이터를 fetch(가져오기)하는 파트
특정한 시기에만 코드를 실행하기 위해 useEffect function을 사용한다.
여기서 작은 팁이 있는데, 함수를 따로 선언하지 않고 바로 실행가능한 함수를 선언할 수 있다.
useEffect(() => {
(() => console.log("바로 실행"))();
}, []);
( )( ); 안의 함수를 바로 실행 가능!
coin 개별화면에서 coins 화면으로 넘어갈 때마다 "Loading..." 문구를 계속 볼 수 있다.
이유는 screen이 바뀔 때 state가 사라지고 마찬가지로 Coins screen에서 coin 개별화면으로 갈때도 가지고 있던 모든 state가 사라지기 때문에 사용자가 다시 돌아올때마다 API를 다시 fetch
하기 때문이다. (바람직한 방법이 아님)
Crypto Icon API : https://coinicons-api.vercel.app/
5.3에서 다른 페이지에서 다시 올 때마다 새로 load 해야 하는 문제점은 여전히 남아있다. 5.4의 코드에서는 Coin component에서 API를 직접 부르지 않으면서 데이터를 넘겨줄 수 있다.
//Coins.tsx
<Link to={`/${coin.id}`} state={coin.name}>
//Coin.tsx
function Coin() {
const [loading, setLoading] = useState(true);
const { coinId } = useParams();
const { state } = useLocation();
console.log(state);
return (
<Container>
<Header>
<Title>{state || "Loading..."}</Title>
</Header>
{loading ? <Loader>Loading...</Loader> : null}
</Container>
);
}
만약 시크릿창에서 coin 개별화면으로 바로 접속하려고 하면 에러가 나거나 정보가 제대로 뜨지 않을 것인데, state가 생성되려면 Home 화면을 먼저 열어야 하기 때문이다. 클릭했을 때 state가 만들어지므로 그 다음 coin 개별화면이 state를 가져서 Title 등을 보여줄 수 있다.
state가 존재하면 name을 가져오고, state가 존재하지 않을때는 "Loading..."문구를 보여주게 된다.
✅1. 따라서 시크릿모드에서 상세화면 URL을 바로 치고 들어오면 Loading 화면을 보게 되고,
✅2. Home 화면을 통해 들어온다면 클릭을 통해 생성된 state를 보게 될 것이다.
바로 위의 코드에서는 시크릿모드로 들어가도 따로 오류화면이 나오지 않는데, 밑의 코드에서는 오류화면이 뜨는 것을 확인할 수 있을것이다.
이 문제점을 해결하기 위해서는 state 뒤에 ?
만 적어주면 된다.
추가되고 달라진 코드는 아래와 같다.
//Coins.tsx
<Link to={`/${coin.id}`} state={coin}>
//Coin.tsx
interface LocationState {
state: {
name: string;
};
}
const { state } = useLocation() as LocationState;
<Title>{state?.name || "Loading..."}</Title>
코인 정보 링크
🔗https://api.coinpaprika.com/v1/coins/btc-bitcoin
코인 가격 정보 링크
🔗https://api.coinpaprika.com/v1/tickers/btc-bitcoin
이제 url을 fetch 해올건데, 코드를 한줄로 쓰는 방법도 존재한다.
const response = await fetch(`https://api.coinpaprika.com/v1/coins/${coinId}`);
const json = await response.json();
//위 두줄과 같은 코드, 캡슐화
const response = await (await fetch(`https://api.coinpaprika.com/v1/coins/${coinId}`)).json();
추후에 React query를 사용해서 더 좋은 코드를 작성해보자.
이제 useState({})를 활용해서 info, price 데이터를 받아올건데 typescript는 항상 빈 object일거라고 생각하기 때문에 오류가 날것이고, 따라서 type을 알려주는 과정이 필요하다. (5.6에서 다뤄본다)
const [info, setInfo] = useState({});
const [price, setPriceInfo] = useState({});
useEffect(() => {
(async () => {
const infoData = await (
await fetch(`https://api.coinpaprika.com/v1/coins/${coinId}`)
).json();
const priceData = await (
await fetch(`https://api.coinpaprika.com/v1/tickers/${coinId}`)
).json();
setInfo(infoData);
setPriceInfo(priceData);
})();
}, []);
TypeScript를 위해 data type을 알아보자.
1. infoData와 priceData 를 console.log로 찍어보기
2. 콘솔창에서 각 데이터 객체를 우클릭하여 전역변수로 object 저장
클릭
3. 각각 temp1, temp2로 저장이 되는데, Object.keys(temp1).join()
을 이용하여 string을 얻고, infoData와 priceData 각각에 넣어서 타입을 써주면 된다.
4. 쉼표를 ctrl+D
해서 모두 선택하고, 지운다음 엔터를 치면 쉽게 작성이 가능!
5. 그 다음 모두 드래그해서 alt+shift+i
를 누르면 선택한 모든 문자열의 가장 우측 끝으로 포커싱이 되므로 :
을 모든 줄에 작성이 가능하다.
6. 이제 value를 가져올건데, cmd창에 Object.values(temp1)
를 입력하면 값을 가져올 수 있는데 타입을 가져오는 코드를 작성해보자. (array의 value 값을 받아서 해당하는 type을 return)
Object.values(temp1).map(v=> typeof v).join()
7. 동일하게 6의 코드를 복붙하고, ctrl+D로 쉼표 제거, 엔터 후 잘라낸 코드를 5의 코드에 alt+shift+로 재선택하여 붙여주면 끝
Nested router : route 안에 있는 또 다른 route
강의내용과 다르게, V6에서는 nested routes를 구현하는 방법에는 두가지가 있다.
✅1. 부모 route의 path 마지막에 /*
를 적어 명시적으로 이 route의 내부에서 nested route가 render 될 수 있음을 표시하고 자식 route를 부모 route의 element 내부에 작성하는 방법
상대경로도 지원하기 때문에 path="chart"로 써도 제대로 동작한다.
//Router.tsx
<Route path="/:coinId/*" element={<Coin />} />
//Coin.tsx
<Routes>
<Route path="price" element={<Price />} />
<Route path="chart" element={<Chart />} />
</Routes>
✅2. 자식 route를 부모 element의 내부가 아닌 route 내부에 작성하는 방법
Router.tsx
<Routes>
<Route path="/" element={<Coins />} />
<Route path="/:coinId" element={<Coin />}>
<Route path="chart" element={<Chart />} />
<Route path="price" element={<Price />}
</Route>
</Routes>
Route를 설정하고, Coin.tsx에서 원하는 곳에 <Outlet />
을 작성해주면 된다.
<Link to={`/${coinId}/chart`}>Chart</Link>
<Link to={`/${coinId}/price`}>Price</Link>
//동일한 코드
<Link to="chart">Chart</Link>
<Link to="price">Price</Link>
useRouteMatch 내가 특정한 URL에 있는지 여부를 알려준다.
V6에서는 useRouteMatch()가 사라지고 useMatch()
를 이용한다. 만약 특정한 URL에 있으면 Object
를 반환하고, 아니면 null
을 반환한다.