본문 바로가기

코딩/Today I Learn

React Js Master #4 useMatch, useQuery, QueryClientprovider

728x90

Nested Router, Nested Route는 

route 안에 있는 또 다른 route이다.

 

price, chart 컴포넌트 각각 생성 

 

 

 

v6 사용하시는 분들 nested routes 작성 방법이 달라진 것 같습니다.

https://ui.dev/react-router-nested-routes/
여기서 알기 쉽게 차근차근 설명해주네요

v6에서 nested routes를 구현하는 방법은 두 가지가 있습니다.
첫 번째는 부모 route의 path 마지막에 /*를 적어 명시적으로 이 route의 내부에서 nested route가 render 될 수 있음을 표시하고 자식 route를 부모 route의 element 내부에 작성하는 방법입니다.

이 방법이 이 영상에서 니코가 하는 방법과 유사합니다. 실제 코딩은 다음과 같습니다.

router.tsx에서

<Route path="/:coinId/*" element={<Coin/>} />

Coin.tsx에서

<Routes>
<Route path="chart" element={<Chart />} />
<Route path="price" element={<Price />} />
</ Routes>

Routes가 상대경로도 지원하기 때문에 path="chart"와 같이 써도 동작한다고 하네요.

두 번째 방법은 자식 route를 부모 element의 내부가 아닌 route 내부에 작성하는 방법입니다.

코드는 다음과 같이 작성합니다.

router.tsx에서
chart와 price 컴포넌트를 import하고

<Route path="/:coinId" element={<Coin />} >
<Route path="chart" element={<Chart />} />
<Route path="price" element={<Price />} />
</Route>

그리고 이 자식 Route들이 어디에 render 될지 부모의 element안에 Outlet을 이용해 표시해주면 됩니다.

Coin.tsx에서, react-router-dom에서 Outlet을 import하고
Overview와 Container 사이에 <Outlet />를 작성해주면 끝납니다.

 

 

                    <Link to={`/${coinId}/chart`}>Chart</Link>
                    <Link to={`/${coinId}/price`}>Price</Link>

                    <Routes>
                        <Route path="chart" element={<Chart />} />
                        <Route path="price" element={<Price />} />
                    </Routes>

 

 

useMatch()

React Router 5버전 => 6버전
useRouteMatch() => useMatch()
현재 위치를 기준으로 지정된 경로에 대한 일치 데이터를 반환합니다.
https://reactrouter.com/docs/en/v6/api#usematch


function Coin() {
    const [loading, setLoading] = useState(true);
    const { coinId } = useParams();
    const state = useLocation().state as RouterState;
    const [info, setInfo] = useState<InfoData>();
    const [priceInfo, setPriceInfo] = useState<PriceData>();

    const priceMatch = useMatch(`/:coinId/price`); 다른 url에서 작동시키면 null나옴 
    const chartMatch = useMatch(`/:coinId/chart`);

...

}

 

const Tab = styled.span<{ isActive: boolean }>` props
    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;
    }
`;

 

 

 

        <Tabs>
                        <Tab isActive={chartMatch !== null}>
                            <Link to={`/${coinId}/chart`}>Chart</Link>
                        </Tab>
                        <Tab isActive={priceMatch !== null}>
                            <Link to={`/${coinId}/price`}>Price</Link>
                        </Tab>
                    </Tabs>

 

 

 


react-query

 

우리가 지금까지 작성해온 useState, useEffect(fetch())등을 fetcher functionuseQuery로 간편하게 작성가능

function Coins() {
    const [coins, setCoins] = useState<ICoin[]>([]);
    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);
        })();
    }, []); 

 

설치

queryClient, QueryClientProvider 

 

index.js

import React from "react";
import ReactDOM from "react-dom";
import { QueryClient, QueryClientProvider } from "react-query";
import { ThemeProvider } from "styled-components";
import App from "./App";
import { theme } from "./theme";

const queryClient = new QueryClient();

ReactDOM.render(
    <React.StrictMode>
        <QueryClientProvider client={queryClient}>
            <ThemeProvider theme={theme}>
                <App />
            </ThemeProvider>
        </QueryClientProvider>

    </React.StrictMode>,
    document.getElementById("root")
);

 

 

2.api 관련된 코드를 컴포넌트와 분리한다.

src> api.ts 파일 생성 

 

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

api를 fetch하고 json하여 return하는 함수 

 

3.useQuery

query의 고유 식별자, fetcher function 두 개의 인자가 필요 

 

import useQuery from "react-Query"

 

const{ isLoading, data}=useQuery("allCoins", fetchCoins);

 

fetcher 함수가 loading 중이라면 reqct query가 isLoading을 통해서 true, false로 알려줌

fetch한 데이터를 data에 넣어줌 

 

 

 

  return (
        <Container>
            <Header>
                <Title>코인</Title>
            </Header>
            {isLoading ? (
                <Loader>"Loading.."</Loader>
            ) : (
                <CoinsList>
                    {data?.slice(0, 100).map((coin) => ( //typescript가 data가 무엇인지 모름
                        <Coin key={coin.id}>
                            <Link
                                to={`/${coin.id}`}
                                state={{ name: coin.name }}
                            >
                                <Img
                                    src={`https://cryptocurrencyliveprices.com/img/${coin.id}.png`}
                                />
                                {coin.name}&rarr;
                            </Link>
                        </Coin>
                    ))}
                </CoinsList>
            )}
        </Container>
    );

 

data와 그 안에 coin을 타입스크립트가 모르니까

   const { isLoading, data } = useQuery<ICoin[]>("allCoins", fetchCoins); //InterfaceCoins으로 타입지정

 

react-query가 데이터를 캐시에 저장해두기 때문에 

이 전과다르게 coins 페이지 -> coin페이지 -> 다시 coins페이지로 돌아올 때 

Loading을 기다리지 않고 바로 정보 나옴 

 


React Query
React 애플리케이션에서 서버 state를 fetching, caching, synchronizing, updating할 수 있도록 도와주는 라이브러리
"global state"를 건드리지 않고 React 및 React Native 애플리케이션에서 데이터를 가져오고, 캐시하고, 업데이트합니다.
```
// Create a client
const queryClient = new QueryClient()

// Provide the client to your App
QueryClientProvider client={queryClient}
```
https://react-query.tanstack.com/quick-start

Queries
쿼리는 서버에서 데이터를 가져오기 위해 모든 Promise 기반 메서드(GET 및 POST 메서드 포함)와 함께 사용할 수 있습니다. 제공한 고유 키는 애플리케이션 전체에서 쿼리를 다시 가져오고, 캐싱하고, 공유하는 데 내부적으로 사용됩니다. useQuery에서 반환된 쿼리 결과에는 템플릿 및 기타 데이터 사용에 필요한 쿼리에 대한 모든 정보가 포함되어 있습니다.
ex) const result = useQuery('todos', fetchTodoList)
https://react-query.tanstack.com/guides/queries
https://react-query.tanstack.com/reference/useQuery#_top

Query Key
React Query는 쿼리 키를 기반으로 쿼리 캐싱을 관리
https://react-query.tanstack.com/guides/query-keys


 


DevTools

react-query에서 제공하는 DevTools

render 할 수 있는 component이고 import 하면 캐시에있는 query를 볼 수있다.

 

app.tsx에서 

 

import { ReactQueryDevtools } from "react-query/devtools";

 

 

function App() {
    return (
        <>
            <GlobalStyle />
            <Router />
            <ReactQueryDevtools initialIsOpen={true} />
        </>
    );
}

 

query들을 볼 수 있다.


Coin.tsx정리하기 

 

fetch함수 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()
    );
}

 

 

 

 

coin.tsx

식별자가 [] 배열로 나타난다는 사실을 알 수 있음


    const { isLoading: infoLoading, data: infoData } = useQuery(
        ["info", coinId], //식별자 구분하기위해
        () => fetchCoinInfo(coinId!) //
이렇게 coinId 뒤에 !만 넣어줬더니 정상작동했습니다
!=> 확장 할당 어션셜로 값이 무조건 할당되어있다고 컴파일러에게 전달해 값이 없어도 변수를 사용할 수 있게 한다고 합니다.
    );
    const { isLoading: tickersLoading, data: tickersData } = useQuery(
        ["tickers", coinId],//식별자 구분하기위해
        () => fetchCoinTickers(coinId!)
    );
    const loading = infoLoading || tickersLoading;
    return (
        <Container>
            <Header>
                <Title>
                    {state?.name
                        ? state.name
                        : loading
                        ? "Loading..."
                        : infoData?.name}
                </Title>
            </Header>
            {loading ?(
                <Loader>"Loading.."</Loader>
            ) : (
                <>
                    <Overview>
                        <OverviewItem>
                            <span>Rank:</span>
                            <span>{infoData?.rank}</span>
                        </OverviewItem>
                        <OverviewItem>
                            <span>Symbol:</span>
                            <span>{infoData?.symbol}</span>
                        </OverviewItem>
                        <OverviewItem>
                            <span>Open Source:</span>
                            <span>{infoData?.open_source ? "Yes" : "No"}</span>
                        </OverviewItem>
                    </Overview>
                    <Description>{infoData?.description}</Description>
                    <Overview>
                        <OverviewItem>
                            <span>Total Suply:</span>
                            <span>{tickersData?.total_supply}</span>
                        </OverviewItem>
                        <OverviewItem>
                            <span>Max Supply:</span>
                            <span>{tickersData?.max_supply}</span>
                        </OverviewItem>
                    </Overview>

 

한 번들어간 것은 캐쉬에 남게되어 로딩을 안 해도 됨