민프

[React][TypeScript] React Query-2 적용 전 / 후 비교 본문

[React]

[React][TypeScript] React Query-2 적용 전 / 후 비교

민프야 2023. 1. 28. 21:42

공식문서

https://react-query-v3.tanstack.com/quick-start

 

Quick Start | TanStack Query Docs

This code snippet very briefly illustrates the 3 core concepts of React Query: Queries

tanstack.com

설치

npm i react-query

 

 

적용 전

function Coins(){
    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);
        })();
    },[]);
    
    return (
        <Container>
            <Header>
                <Title>코인</Title>
            </Header>
            {loading ? <Loader>Loading...</Loader>:(
            <CoinsList>
                {coins.map((coin) =>
                (
                <Coin key={coin.id}>
                    <Link to={`/${coin.id}`} state={coin.name}>
                         <Img src={`https://coinicons-api.vercel.app/api/icon/${coin.symbol.toLocaleLowerCase()}`}/>
                        {coin.name} &rarr;
                    </Link>
                </Coin>)
                )
                }
            </CoinsList>
            )}
        </Container>
    );
}

export default Coins;

위 코드에서 데이터를 위한 state, 로딩을 위한 state가 있고, useEffect에서 fetch를 사용하여서 데이터를 가지고 왔다. 

그리고 데이터가 준비되면 데이터를 State에 넣고 로딩을 false로 두었다. 

이 과정을 react query는 자동으로 해결해준다. 

 

적용 후 

import { useEffect, useState } from "react";
import { useQuery } from "react-query";
import { Link, useParams } from "react-router-dom";
import styled from "styled-components";
import { fetchCoins } from "../api";

const Container = styled.div`
  padding: 0px 20px;
  max-width: 480px;
  margin: 0 auto;
`;

const Header = styled.header`
  height: 10vh;
  display: flex;
  justify-content: center;
  align-items: center;
`;

const CoinsList = styled.ul``;

const Coin = styled.li`
  background-color: white;
  color: ${(props) => props.theme.bgColor};
  margin-bottom: 10px;
  border-radius: 10px;
  cursor: pointer;
  a {
    padding: 20px;
    align-items: center;
    transition: color 0.1s ease-in;
    display: flex;
  }
  &:hover {
    a {
      color: ${(props) => props.theme.accentColor};
    }
  }
`;

const Title = styled.h1`
  font-size: 48px;
  color: ${(props) => props.theme.accentColor};
`;

const coins = [
  {
    id: "btc-bitcoin",
    name: "Bitcoin",
    symbol: "BTC",
    rank: 1,
    is_new: false,
    is_active: true,
    type: "coin",
  },
  {
    id: "eth-ethereum",
    name: "Ethereum",
    symbol: "ETH",
    rank: 2,
    is_new: false,
    is_active: true,
    type: "coin",
  },
  {
    id: "hex-hex",
    name: "HEX",
    symbol: "HEX",
    rank: 3,
    is_new: false,
    is_active: true,
    type: "token",
  },
];

const Loader = styled.span`
  text-align: center;
  display: block;
`;

const Img = styled.img`
  width: 25px;
  height: 25px;
  margin-right: 10px;
`;

interface ICoinInterface {
  id: string;
  name: string;
  symbol: string;
  rank: number;
  is_new: boolean;
  is_active: boolean;
  type: string;
}

function Coins() {
  const { isLoading, data } = useQuery<ICoinInterface[]>("allCoins", fetchCoins);
  // 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);
  //     })();
  // },[]);

  return (
    <Container>
      <Header>
        <Title>코인</Title>
      </Header>
      {isLoading ? (
        <Loader>Loading...</Loader>
      ) : (
        <CoinsList>
          {data?.map((coin) => (
            <Coin key={coin.id}>
              <Link to={`/${coin.id}`} state={coin.name}>
                <Img
                  src={`https://coinicons-api.vercel.app/api/icon/${coin.symbol.toLocaleLowerCase()}`}
                />
                {coin.name} &rarr;
              </Link>
            </Coin>
          ))}
        </CoinsList>
      )}
    </Container>
  );
}

export default Coins;

 

적용 전 / 후의 코드를 보면 확연하게 코드가 줄어든 것을 볼 수 있다.

그럼 react query는 어떻게 사용하는 것 일까?

1. fetcher 함수를 만들자

위 코드에서 fetcher 함수는 useEffect 부분에서

const response = await fetch("https://api.coinpaprika.com/v1/coins");
const json = await response.json();

이 부분 이다. 

이러한 API와 관련 된 것들은 Component들과 섞이지 않도록 api.tsx라는 파일 안에 넣을 것 이다.

이런 fetcher함수는 꼭 fetch promise를 return해줘야한다. 

------api.ts------


export function fetchCoins() {
   return fetch("https://api.coinpaprika.com/v1/coins").then((response)=> response.json());
}

이런식으로 response를 json으로 리턴해주는 Fetcher함수를 만들었다. 

2. useQuery를 작성하자

useQuery는 2가지 argument를 필요로 하는데

2-1. queryKey

queryKey는 고유식별자다  

2-2. fetcherFunction

api.ts에 있는 fetchCoins() 와 같은 함수이다. 

  const { isLoading, data } = useQuery<CoinInterface[]>("allCoins", fetchCoins);

이렇게 작성한 useQuery라는 hook은 isLoading이라고 불리는 boolean 값을 return해주는데 

useQuery가 fetcher 함수 fetchCoins를 불러오고

fetcher 함수가 isLoading(boolean)이라면 false, true라면 return json값을 CoinInterface가 적용 된 data라는 Property로 받을 것 이다.

 

이렇게 하면 react query를 사용하여 fetch를 진행할 수 있다. 

 

이때 달라진 점이 있다.

뒤로가기를 하였을 때 정보가 새로고침 되지 않고, 로딩이 되지 않는다. 

이유는 무엇일까?

react-query가 데이터를 "allCoins" 라는 이름으로 캐시에 저장해두기 때문이다. 

react-query는 데이터를 파괴하지 않고 데이터를 유지하고 있는다.

 

캐시를 직접 컨트롤 하는건 까다롭지만 react query를 이용하면 보다 쉽게 캐시를 이용할 수 있다. 

 

조금 더 응용을 해보자

fetch함수에 argument를 넘기고 잘 동작하는지 확인해보자

api.ts

const BASE_URL = 'https://api.coinpaprika.com/v1'

export function fetchCoins() {
   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());
}

위 코드와 같이 fetch함수에 coinId argument를 받아보려고 한다.

이때 useQuery는 어떻게 작성해야할까?

  const { isLoading: infoLoading, data: infoData } = useQuery(
    ["info", coinId],
    () => fetchCoinInfo(coinId)
  );
  const { isLoading: priceLoading, data: priceData } = useQuery(
    ["price", coinId],
    () => fetchCoinTickers(coinId)
  );

여기에서 만약 useQuery의 key를 정하는 곳에서 coinId로만 하였다면 캐시의 이름이 겹쳐서 좋지 않을 것 이다. 그래서 'info', 'price'의 이름을 붙여서 차별점을 주었고, return들에 대해서도 isLoading 뒤에 : infoLoading의 이름과 데이터 이름을 붙여서 차별점을 주었다. 

 

결과화면은 밑 사진과 같다.

Comments