PJH's Shopping Mall - 상품 리스트, 상세페이지

박정호·2022년 12월 20일

Shopping Project

목록 보기
2/11
post-thumbnail

🚀 Start

우선 상품 데이터들을 dummy API를 이용하여 dummy data로 가져와보고, 상품에 따른 상세페이지도 구현해보자.

이때, React Query를 이용하여 데이터를 조회하도록 하자!

React Query

✅ 설치

yarn add react-query

1️⃣ QueryClientProvider

  • React Query는 캐시를 관리하기 위해 QueryClient 인스턴스를 사용한다.
// src/queryClient.ts
import { QueryClient } from "react-query";

export const getClient = (() => {
  let client: QueryClient | null = null;
  return () => {
    if (!client) client = new QueryClient(); // 인스턴스 생성
    return client;
  };
})();
  • 컴포넌트가 useQuery 훅 안에서 QueryClient 인스턴스에 접근할 수 있도록 QueryClientProvider를 컴포넌트 트리 상위에 추가해줘야 한다.
// src/app.tsx
import { getClient } from "./queryClient";
import { QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";

const App = () => {
  const elem = useRoutes(routes);
  const queryClient = getClient();
  return (
    <QueryClientProvider client={queryClient}>
      {elem}
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
};

export default App;

ReactQueryDevtools

  • React Query는 전용 개발 도구와 함께 제공되며, 이를 통해 React Query의 모든 내부 작업을 시각화하는데 도움을 주며, 문제 발생시 디버깅 시간을 절약한다.
  • 개발 도구를 기본적으로 열도록 하려면 true로 설정해야 한다.

💡 참고하자!
👉 React Query HomePage
👉 React Query로 서버 상태 관리하기



👉 React Query 캐시 정책

React Query의 캐싱개념은 stale과 cachetime을 통해 이루어진다.

React Query는 기본적으로 캐싱된 data를 stale한 상태로 여긴다. (참고)

  • stale의 사전적 의미는 신선하지 않는이라는 뜻을 가진다.
  • stale이란 최신화가 필요한 데이터라는 의미로 stale한 상태가 되면 다음의 경우에 refetch 된다.

💡 refetch 되는 조건

1️⃣ 새로운 query instance가 마운트 될 때 (= page를 이동 했다가 왔을 때)

2️⃣ 브라우저 화면을 이탈 했다가 다시 focus 할 때

3️⃣ 네트워크가 다시 연결될 때

4️⃣ 특별히 설정한 refetch interval에 의한 경우 (refetchInterval)

만약 해당 useQuery를 호출할 당시에 옵션으로 staletime을 따로 지정해주지 않는다면?

항상 캐싱되어 있는 데이터는 stale하다고 여기기 때문에 refetching을 하게 되어 서버에 계속적인 요청을 하게된다.

즉, staletime을 지정해주지 않고 쓴다면 react-query의 캐싱 기능을 제대로 활용할 수가 없다.


  • cacheTime

    • 데이터가 inactive 상태일 때 캐싱된 상태로 남아있는 시간
    • 쿼리 인스턴스가 unmount되면 데이터는 inactive 상태로 변경되며, 캐시는 cacheTime 만큼 유지.
    • cacheTime이 지나지 않은 캐시 데이터를 보여주고, cacheTime이 지나면 가비지 콜렉터로 삭제
  • staleTime

    • 기본값은 0으로, 쿼리 데이터가 fresh에서 stale로 전환되는데 걸리는 시간
    • Infinity로 설정하면 쿼리 데이터는 직접 캐시를 무효화할 때까지 fresh 상태로 유지
    • 캐시는 메모리에서 관리되므로 브라우저 새로고침 후에는 다시 가져온다.
    • default: 0
  • refetchOnWindowFocus

    • 윈도우가 다시 포커스되었을 때 데이터를 호출할 것인지 여부
    • default: true
  • refetchOnReconnect

    • 네트워크가 끊어졌다가 다시 연결될 때 데이터를 호출할 것인지 여부
    • default: true
  • refetchOnMount

    • mount되었을 때 refetch 여부를 결정
    • true라면 stale 상태일 때 refetch
    • default: true

✅ 상품리스트에서 상세페이지로 이동후 다시 상품리스트로 이동할 때 캐싱된 데이터를 불러오므로 새롭게 데이터를 불러오지 않는 것을 확인할 수 있다!

전체적인 cacheTime, staleTime은 만료 시간을 (Infinity으로 설정하여 API 추가 호출을 방지하고, 자주 데이터 통신이 오가는 부분에서 별도로 Time 설정을 해주자.

// queryClient.ts
...
client = new QueryClient({
        defaultOptions: {
          queries: {
            cacheTime: infinity,
            staleTime: infinity,
            refetchOnMount: false, 
            refetchOnReconnect: false,
            refetchOnWindowFocus: false, 
          },
        },
      });


2️⃣ Query Keys

기본적으로 React Query는 queryKey를 통해 query cache와 상호작용이 가능하다.

  • 쿼리 키는 useQuery마다 부여되는 고유 key값이다.
  • 쿼리 키는 문자열처럼 단순할 수도 있고 많은 문자열 및 중첩 개체의 배열만큼 복잡할 수 있다.
  • 쿼리 키는 직렬화 가능하고 쿼리 데이터에 고유한 한 사용할 수 있다.
<Example>
// 문자열
const res = useQuery('persons', queryFn);

// 배열 - 1
const res = useQuery(['persons'], queryFn);

// 배열 - 2
const res = useQuery(['persons', 'add Id'], queryFn);

// 배열 - 3
const res = useQuery(['add Id', 'persons'], queryFn);

// 배열 - 4
const res = useQuery(['persons', {type: 'add', name: 'Id'}], queryFn);

객체로 Key 관리

  • 위의 예는 key를 하드코딩으로 작성하였다. 따라서, 휴먼에러가 발생하기 쉽고 Key를 수정해야하는 경우 유지보수가 어렵다. 따라서, 객체로 작성하여 관리하자.
// src/queryClient.ts
export const QueryKeys = {
  PRODUCT: "PRODUCTS",
};

💡 참고하자!
👉 React Query Key 관리



3️⃣ useQuery

그렇다면 앞서 생성한 querykey를 어떻게 사용하면 될까? 바로 useQuery를 사용하면 된다!

useQuery는 서버로부터 데이터를 조회해올 때 사용한다.

// 대표적으로 사용되는 3개의 인자
const { data, isLoading, error } = useQuery(queryKey, queryFn, options)

queryFn

  • queryFn은 query Function으로 promise 처리가 이루어지는 함수로, fetcher, Axios 등을 이용해 서버에 API 요청을 하는 코드라고 생각하면 된다.
// src/queryClient.ts
const BASE_URL = "https://fakestoreapi.com";

export const fetcher = async ({ method,path,params}: {
  method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
  path: string;
  body?: any;
  params?: any;
}) => {
  try {
    const url = `${BASE_URL}${path}`;
    const fetchOptions: RequestInit = {
      method,
      headers: {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": BASE_URL,
      },
    };
    const res = await fetch(url, fetchOptions);
    const json = await res.json();
    return json;
  } catch (err) {
    console.error(err);
  }
};

👉 상품리스트

// src/pages/products/index.tsx
import { useQuery } from "react-query";
import { fetcher, QueryKeys } from "../../queryClient";
import { Product } from "../../types";

const ProductList = () => {
  const { data } = useQuery<Product[]>(QueryKeys.PRODUCT, () =>
    fetcher({
      method: "GET",
      path: "/products", //dummy api params
    })
  );

  return (
    <ul className="products">
      {data?.map((product: Product) => (
        <ProductItem {...product} key={product.id} />
      ))}
    </ul>
  );
};

export default ProductList;


👉 상품 상세페이지

import { Product } from "../../types";
import { useQuery } from "react-query";
import { fetcher, QueryKeys } from "../../queryClient";
import { useParams } from "react-router-dom";

const ProductDetail = () => {
  const { id } = useParams(); // URL의 파라미터 가져오기
  const { data } = useQuery<Product>([QueryKeys.PRODUCTS, id], () =>
    fetcher({
      method: "GET",
      path: `/products/${id}`,
    })
  );

  if (!data) return null;

  return <ProductDetail item={data} />;

};

export default ProductDetail;

💡 참고하자!
👉 React Query의 useQuery에 대해 알아보기

💡 잠깐) dummy API

Mockup API라고 할 수 있는데, Mockup(모형) API Server는 말 그대로 가짜 API 서버이다.

클라이언트 요청에 실제 서버처럼 동작하기보다는 미리 저장된 데이터를 단순하게 돌려주는 형태이다. 다시 말해 이는 가짜 서버를 사용해 실제 서버와 통신하는 것처럼 만들 수 있다.

나는 핑 웹사이트 프로토타입을 위한 Fake Store Rest API를 사용하였다.

fakeStoreApi는 JSON 형식의 제품, 카트 및 사용자가 필요한 모든 유형의 쇼핑 프로젝트와 함께 사용할 수 있다.

profile
기록하여 기억하고, 계획하여 실천하자. will be a FE developer (HOME버튼을 클릭하여 Notion으로 놀러오세요!)

0개의 댓글