Crypto tracker #3 Nested Routes

Leesu·2022년 11월 22일
0

이제 Coin.tsx 페이지에 본격적으로 데이터를 가져올 시간.
코인의 정보를 알려주는 url, 해당 코인의 가격이 상세하게 나와있는 url 두 가지를 사용해서 구현하는 것이 목표!
https://api.coinpaprika.com/v1/coins/btc-bitcoin
https://api.coinpaprika.com/v1/tickers/btc-bitcoin


Coin.tsx

  • 코인 아이디는 이미 가지고 있으므로, useEffect 를 사용해 2가지 api 를 fetch 해준다.
function Coin() {
  const [loading, setLoading] = useState(true);
  const { coinId } = useParams<RouteParams>(); <<<--- coinId!!
  const { state } = useLocation<RouteState>();
  const [info, setInfo] = useState({});
  const [priceInfo, 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);
    })();
  }, []);
  return (
  • 그리고 각각 setInfo , setPriceInfo 안에 데이터를 넣어줬다.
  • 그러나 이 상태로 바로 사용하려하면, 타입스크립트에서 에러가 난다.
    • 이유는, info, priceInfo 둘은 타입스크립트가 보자면 '엥 넌 비어있는 obj 아님?;;' 하고 에러를 띄우기 때문.
    • 그래서 늘 그랬듯 interface 를 사용해 타입스크립트에게 어떤 정보들이 있고 어떤 데이터 타입들인지 알려주어야 한다!
interface InfoData {
  id: string;
  name: string;
  symbol: string;
  rank: number;
  is_new: boolean;
  is_active: boolean;
  type: string;
  description: string;
  message: string;
  open_source: boolean;
  started_at: string;
  development_status: string;
  hardware_wallet: boolean;
  proof_type: string;
  org_structure: string;
  hash_algorithm: string;
  first_data_at: string;
  last_data_at: string;
}
  • json 파일을 보면서 복붙복붙 적어주면 되는데, 이때 유용했던 단축키들은 하단에!
  • 간단 순서
    • 콘솔에 obj 을 띄워놓고, 'store object as global varible' 을 클릭하면
      'temp1' 이라는 전역 변수에 저장 된다.
    • object.keys(temp1).join() 코드를 실행하면 obj 안에 있던 데이터들이 string 한줄로 출력됨! 복붙 ㄱ
    • string, number .. 데이터 타입들도 위와 같은 방법으로
      object.values(temp1).map( v => typeof v).join()
      type을 뽑아낸 뒤에, 복붙하면 된다.
    • 혹여, string, number 가 아닌 array가 있을 경우, 추가 interface 를 만들어
      tags : ITag[]; 이런식으로 직접 설명해주어야한다.
      (그러나 tags 는 필요가 없어서 그냥 삭제)
  • 다 만들었다면 알려주자.
const [info, setInfo] = useState<InfoData>();
  • interface PriceData 도 동일한 방법으로 하면 되는데,
    quotes 항목이 object 이므로 설명해준다.
interface PriceData {
    .
    .
    .
 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;
      percent_change_12h: number;
      percent_change_15m: number;
      percent_change_24h: number;
      percent_change_30d: number;
      percent_change_30m: number;
      percent_from_price_ath: number;
      price: number;
      volume_24h: number;
      volume_24h_change_24h: number;
    };
  };
}
  • 이렇게...
  • 아니 이거 너무 노가다 아닌강용 실제로 이렇게 쓰시나?;; 다른 방법이 있는지 찾아봐야겠음..

Nested Routes(1)

  • 데이터를 다 받아왔으니, screen 에 뿌려줄 차례

  • 총 세개의 컴포넌트를 만들어 화면을 만들었다.

  • 하단에는 가격 데이터로 chart 를 보여주고, price 에서는 가격변동 데이터를 보여줄 예정

  • chart, price 탭을 Nested Routes 통해 '/btc-bitcoin/chart' , '/btc-bitcoin/price' 와 같이 만들 것

  • 우선 2개의 route 를 만들어야 한다.

  • 'Price.tsx' / 'Chart.tsx' 생성!

  • 이제 'Coin.tsx' 페이지에 route 를 추가해주어야하는데..

  • 강의에서는 react router-dom 5.3 버전을 사용하나, 나는 6.2버전을 사용중이여서 적용되지 않았다.

    • 이에 하단의 참고 문헌을 보며 작성하였고, 두 가지 방법 중 <Outlet>을 사용하지 않는 방법으로 채택했다.
- Router.tsx

function Router() {
  return (
    <BrowserRouter>
      <Routes>
        {/* nested route 으로 자식 route 는 상대경로로 작성한다. */}
        <Route path="/" element={<Coins />}></Route>
        <Route path="/:coinId" element={<Coin />}></Route>
        <Route path="/:coinId/*" element={<Coin />} />  <<<----
      </Routes>
    </BrowserRouter>
  );
}
  • 마지막에 /*를 적어 명시적으로 이 route의 내부에서
    nested route가 render 될 수 있음을 표시하고, 자식 route를 부모 route의 element 내부에 작성하는 방법을 사용
- Coin.tsx

<Routes>
  <Route path="chart" element={<Chart />} />
  <Route path="price" element={<Price />} />
</Routes>
  • Routes가 상대경로도 지원하기 때문에 path="chart"와 같이 써도 동작한다고 한다.
  • 다른 방법은, 자식 route를 부모 element의 내부가 아닌 route 내부에 작성하는 방법인데,
- router.tsx

<Route path="/:coinId" element={<Coin />} >
<Route path="chart" element={<Chart />} />
<Route path="price" element={<Price />} />
  • 이렇게 작성한 다음에, 자식 route 들이 어느곳에 render 될지 부모 element 안에 Outlet 을 이용해 표시해주면 된다고 한다.
- Coin.tsx

import Outlet from "react-router-dom"

 ...
   넣고자하는 곳에 <Outlet />  작성

Outlet (중첩 라우트 사용하기)
자식 라우트의 엘리먼트가 있는 경우 렌더링한다.
Outlet은 부모 경로 요소에서 자식 경로 요소를 렌더링하는 데 사용해야 합니다.
이를 통해 하위 경로가 렌더링될 때 중첩된 UI를 표시할 수 있습니다. 부모 라우트가 정확히 일치하면 자식 인덱스 라우트를 렌더링하거나 인덱스 라우트가 없으면 아무것도 렌더링하지 않습니다.

  • Outlet은 이해가 더 완전히 되면 사용하기 위해 우선 첫번째 방법을 사용했다.

Nested Routes(2)

  • 이제 chart, price 탭을 만들어보자.
  • 특정 url 로 이동하게하는 Link 를 사용
- Coin.tsx

<Tabs>
  <Tab>
    <Link to={`/${coinId}/chart`}>Chart</Link>
  </Tab>
  <Tab>
    <Link to={`/${coinId}/price`}>Price</Link>
  </Tab>
</Tabs>
  
<Routes>
  <Route path="chart" element={<Chart />} />
  <Route path="price" element={<Price />} />
</Routes>
  • 완성!
  • 그럼 이제 유저가 어느 탭에 있는지 알려줄 차례다.
  • 그러기 위해서 특정한 url 에 있는지 여부를 알려주는 useMatch() hook 을 사용해줬다.
  • 특정 url 에 들어가있다면? object 를 반환받고, 그 안에 isExact : true 를 반환, 아니라면 null 을 반환.
  const priceMatch = useMatch("/:coinId/price");
  const chartMatch = useMatch("/:coinId/chart");
  • 이제 <Tab> 컴포넌트에 추가해주면 된다 :)
  • 그 전에, <Tab> 컴포넌트에 isActive 라고 불리는 prop 을 추가했다.
const Tab = styled.span<{ isActive: boolean }>`

isActive / 속성값 : Boolean
창이 활성 상태이면 true이고, 그렇지 않으면 false. 기본값 false

- Coin.tsx

<Tabs>
  <Tab isActive={chartMatch !== null}>    <<<----
    {/* chartMatch 에서 chart(url)이 맞지 않으면 null나옴 */}
    <Link to={`/${coinId}/chart`}>Chart</Link>
  </Tab>
  <Tab isActive={priceMatch !== null}>    <<<----
    <Link to={`/${coinId}/price`}>Price</Link>
  </Tab>
</Tabs>
  • Done!

profile
기억력 안 좋은 FE 개발자의 메모장

0개의 댓글