Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
Tags
- 42747
- Helmet
- 귤 고르기
- 오블완
- usesetrecoilstate
- 138476
- Outlet
- React
- useoutletcontext
- 프로그래머스
- userecoilvalue
- Recoil
- 노마드코더
- H-index
- Typescript
- 티스토리챌린지
- programmers
Archives
- Today
- Total
오늘도 코딩하나
[React] CRYPTO TRACKER #1 - useLocation, Nested Router, React-Query 본문
React/노마드코더
[React] CRYPTO TRACKER #1 - useLocation, Nested Router, React-Query
오늘도 코딩하나 2025. 2. 18. 19:29강의 내용: 강의 링크
#5.0 ~ #5.11
✅ 페이지간 데이터 전달 기능
➰ useLocation
- 현재 URL 정보를 가져오는 hook
- pathname, search, hash, state, key 등의 정보 제공
<Link to={`/${coin.id}`} state={{ name: coin.name }}>
<Img
src={`https://cryptocurrencyliveprices.com/img/${coin.id}.png`}
/>
{coin.name} →
</Link>
const { state } = useLocation();
<Title>
{state?.name ? state.name : loading ? "Loading..." : infoData?.name}
</Title>
- useLocation().state를 사용하면 URL에 보이지 않는 방식으로 데이터를 전달할 수 있음
⇒ 비하인드더씬
- /${coin.id} 페이지로 이동하면서 state 값을 함께 전달
- useLocation().state에서 전달된 값을 받아서 사용
✅ 중첩 라우트
➰ nested router
- 부모 경로 안에 자식 경로를 중첩해서 정의하는 방식
- route안에 있는 또 다른 route
(tab or 스크린 안에 많은 섹션이 나뉘어진 곳) - 페이지 내부에서 페이지 이동없이 또 다른 페이지에 방문할 수 있게 해줌
- Router.tsx
<Route path="/:coinId" element={<Coin />}>
<Route path="price" element={<Price />} />
<Route path="chart" element={<Chart />} />
</Route>
- Coin.tsx
const priceMatch = useMatch("/:coinId/price");
const chartMatch = useMatch("/: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>
<Outlet />
- useMatch : 현재 URL이 특정 패턴과 일치하는지 확인하는 Hook ( 특정 경로에 있는지 여부 판단 )
✅ React-Query
➰ react-query
- 서버 상태 관리 라이브러리
- fetching, caching, synchronizing, updating을 도와주는 라이브러리
▶ 기존 방식: useState + useEffect
const [coins, setCoins] = useState<CoinInterface[]>([]);
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);
})();
}, []);
- setLoading(false) : 로딩 상태를 직접 관리해야함
- 데이터 캐싱 및 리페칭 기능 없음
매번 컴포넌트가 마운트될 때마다 API 요청이 발생함(Loading...) - 코드가 길어지고 가독성이 떨어짐
▶ react-query 방식
import { useQuery } from "@tanstack/react-query";
const { isLoading, data } = useQuery<ICoin[]>({
queryKey: ["allCoins"],
queryFn: fetchCoins,
});
- 로딩 상태를 자동 관리
별도의 useState 필요 없이 isLoading 값이 자동으로 업데이트됨 - 코드가 간결하고 가독성이 좋음
- 데이터 캐싱 및 리페칭 지원
동일한 요청에 대해 자동 캐싱, 브라우저 포커스 변경 시 자동 리페칭 가능
→ 데이터를 저장했다가 필요한 시점에 자동으로 갱신하는 것! - API 요청을 최소화
같은 요청을 반복해서 보내지 않도록 캐시에서 데이터를 제공
→ 불필요한 로딩이 발생하지 X - fetcher함수 : API를 fetch 하고 json을 return하는 함수
- api.ts
const BASE_URL = `https://api.coinpaprika.com/v1`;
export function fetchCoins() {
// json data의 Promise를 return해야함
return fetch(`${BASE_URL}/coins`).then((response) => response.json());
}
export function fetchCoinInfo(coinId: string) {
return fetch(`${BASE_URL}/coins/${coinId}`).then((response) =>
response.json()
);
}
export function fetchCoinTickers(coinId: string) {
return fetch(`${BASE_URL}/tickers/${coinId}`).then((response) =>
response.json()
);
}
- Coin.tsx
const { coinId } = useParams();
const { isLoading: infoLoading, data: infoData } = useQuery<IInfoData>({
queryKey: ["info", "coinId"],
queryFn: () => fetchCoinInfo(coinId),
});
const { isLoading: tickersLoading, data: tickersData } = useQuery<PriceData>({
queryKey: ["tickers", "coinId"],
queryFn: () => fetchCoinTickers(coinId),
});
- queryKey는 캐싱을 위한 고유 식별자 역할을 한다.
- 같은 coinId를 사용해도 "info"는 코인 기본 정보, "tickers"는 가격 정보를 의미하도록 데이터 유형을 구분한다.
❓ ❓ 근데 오류가 발생했다. ❓ ❓
Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
Type 'undefined' is not assignable to type 'string'.
- TypeScript의 "Type Incompatibility Error" (타입 불일치 오류)
const { coinId } = useParams();
const { isLoading: infoLoading, data: infoData } = useQuery<IInfoData>({
queryKey: ["info", "coinId"],
queryFn: () => fetchCoinInfo(coinId as string),
enabled: !!coinId,
});
const { isLoading: tickersLoading, data: tickersData } = useQuery<PriceData>({
queryKey: ["tickers", "coinId"],
queryFn: () => fetchCoinTickers(coinId as string),
enabled: !!coinId,
});
- coinId가 string|undefined로 되어있어 타입 오류가 발생했다.
여러가지 방법이 있는데 나는 as string, enabled를 적용시켜줬다. - as string
→ TypeScript의 타입 에러 방지
→ coinId를 강제로 string 취급, 하지만 coinId가 실제로 undefined일 경우 런타임 에러 발생
(fetchCoinInfo(undefined) → API 요청 실패) - enabled: !!coinId
→ 실제 실행 방지
→ coinId가 undefined일 때 queryFn 자체가 실행되지 않음
* !! (Boolean 변환 연산자) : Falsy한 값은 false로, Truthy한 값은 true로 변환되며, 한 번 변환된 값은 바뀌지 않는다.
🔎 TypeScript 타입 체크
1. 변수 선언 시
const { coinId } = useParams(); // coinId: string | undefined
- TypeScript는 여기서는 coinId가 string | undefined 라는 타입 정보만 할당
- 실제 값이 string인지 undefined인지까지 체크하지 않음
⇒ 이 단계에서는 에러 발생 ❌
2. 함수 실행 시
queryFn: () => fetchCoinInfo(coinId), // ❌ TypeScript 오류 발생
- fetchCoinInfo는 string을 받아야 하는데, coinId는 string | undefined
- TypeScript는 coinId가 undefined일 가능성을 감지하고 에러 발생!
📌 TypeScript는 변수를 선언할 때는 "이 변수가 가질 수 있는 모든 타입"을 기록해 두고,
이 변수를 사용할 때 그 타입이 안전한지 확인하는 방식
🔎 coinId type error 해결방법
강의 댓글창에서 사람들이 다양한 방법으로 type error를 해결했고, 그 방법을 정리해봤다.
1. as string 👉 (가장 추천)
fetchCoinTickers(coinId as string);
- coindId가 undefined일 경우 런타임 오류 발생
- TypeScript의 타입 체크를 무시하기 때문에 안전하지 않음
👉 단독으로 쓰는 것은 위험하지만, enabled: !!coinId와 함께 쓰면 안전해짐!
2. string | undefined
function fetchCoinTickers(coinId: string | undefined) {...}
- 함수 내부에서 undefined를 체크할 수 있어 안전함
- 하지만 매번 if(!coinId) return; 같은 체크 로직을 작성해야함
없으면, undefined가 그대로 전달되어 잘못된 API 요청이 될 수 있음 - coinId를 받기 전에 아예 undefined가 안들어오도록 타입을 보장하는게 더 깔끔한 해결책임
3. ${coinId} 👉 (비추천)
fetchCoinTickers(`${coinId}`);
- undefined일 경우 "undefined"(문자열)로 변환되므로 TypeScript 오류 없음
- API 요청이 fetchCoinTickers("undefined")처럼 잘못된 값으로 가게 됨
'React > 노마드코더' 카테고리의 다른 글
[React] Crypto Tracker #2 - Props 없이 상태 관리하기 (Nested Router & Recoil) (1) | 2025.02.20 |
---|---|
[React] React JS 마스터클래스_React Router v6에서 달라진 점 (0) | 2025.02.19 |
[React] React Router v6 살펴보기 (2) | 2025.01.16 |
[React] TypeScript로 component 설계하기 (1) | 2025.01.16 |
[React] Styled-Component로 동적 스타일링과 테마 적용하기 (0) | 2025.01.15 |