[E-commerce PJT]_MSW 및 GraphQL 적용하기 - 2

hanseungjune·2023년 8월 28일
post-thumbnail

⭐ Mock Service Worker로 Fake API를 만들고 GraphQL로 특정 Data만 가져올 수 있게!

쇼핑몰의 어느 정도 틀을 마련하고... 이제 어떻게 API를 백엔드로부터 받아올지에 대한 고민이 생겼다. 사실상 백엔드를 구현해본적이 없기 때문에 클라이언트 측에서는 어떻게 코드를 미리 작성할지 감이 안오는 것이다. 그렇다고 WEB-API를 그대로 들고 오기에는 프로젝트의 의미가 없다고 판단했었다.

그래서... Mock API를 만들어보자는 생각으로 MSW를 하게되었다. 근데 그냥 REST API를 받아왔던 경험은 많이 해봤었기 때문에 GraphQL로 받아오는 작업을 해보자는 생각으로 GraphQL로 진행하게 되었다. 그리고 상태관리를 Recoil로 해서 중앙집중식 Redux와 어떤 차이점이 있는지 알아보려고 적용했다. (물론 하다가 필요없어서 다시 지웠다)

자 그럼 출발해볼까!

📃 작성 코드 및 해석

1. 목표

  • MSW를 통해서 상품 목록에 대한 Mock API 만들기
  • MSW를 통해서 상품 상세 정보에 대한 Mock API 만들기
  • MSW를 통해서 장바구니 정보에 대한 Mock API 만들기
  • 특정 데이터 쿼리만 받아오는 GraphQL 적용시키기
  • 장바구니 데이터 상태 전역으로 관리하기

2. 기본 설치 및 세팅

🔆 package.json

{
  "name": "shopping-mall",
  "private": true,
  "version": "0.0.0",
  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "graphql": "^16.8.0",
    "graphql-request": "^6.1.0",
    "graphql-tag": "^2.12.6",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-query": "^3.39.3",
    "react-router-dom": "^6.15.0",
    "recoil": "^0.7.7",
    "uuid": "^9.0.0"
  },
  "devDependencies": {
    "@types/react": "^18.2.15",
    "@types/react-dom": "^18.2.7",
    "@types/uuid": "^9.0.2",
    "@vitejs/plugin-react": "^4.0.3",
    "msw": "^1.2.4",
    "sass": "^1.66.1",
    "typescript": "^5.0.2",
    "vite": "^4.4.5"
  },
  "msw": {
    "workerDirectory": "public"
  }
}

dependencies

  1. 추가된 종속성:
    • graphql: ^16.8.0
    • graphql-request: ^6.1.0
    • graphql-tag: ^2.12.6
    • recoil: ^0.7.7
    • uuid: ^9.0.0

devDependencies

  1. 추가된 개발 종속성:
    • @types/uuid: ^9.0.2
    • msw: ^1.2.4

그 외 설정

  1. msw 설정이 추가되었습니다
"msw": {
   "workerDirectory": "public"
}
  • 이 설정은 msw (Mock Service Worker)를 사용하여 개발 중에 API 목업을 만드는 데 도움을 줍니다. 설정은 msw worker 파일이 public 디렉토리에 위치하도록 지정합니다.

  • public에 mockServiceWorker.ts 파일 설치하기

npx msw init public/

정리

  • GraphQL 관련 패키지 (graphql, graphql-request, graphql-tag)가 추가되었습니다. 이는 아마도 애플리케이션에서 GraphQL API를 사용하도록 업데이트되었기 때문일 것입니다.

  • Recoil은 상태 관리 라이브러리로, 이 라이브러리의 추가는 애플리케이션에서 Recoil을 사용하여 상태를 관리하기 시작했음을 의미합니다.

  • UUID는 고유한 ID를 생성하기 위한 유틸리티입니다. 아마도 애플리케이션 내에서 고유한 ID를 생성할 필요가 있어서 추가되었을 것입니다.

  • 개발 종속성에서, msw는 개발 중에 백엔드 서비스를 목업하기 위해 사용되는 도구입니다. 이는 프런트엔드 개발 시 실제 백엔드 서비스 없이 API 응답을 모방할 수 있게 해줍니다.

3. MSW

🔆 handlers.ts

import { graphql } from "msw";
import { GET_PRODUCT, GET_PRODUCTS } from "../graphql/products";
import { ADD_CART, CartType, GET_CART } from "../graphql/cart";

const mockProducts = (() =>
  Array.from({ length: 20 }).map((_, i) => ({
    id: i + 1 + "",
    imageUrl: `https://placebeard.it/200x150/${i + 1}`,
    price: 50000,
    title: `임시상품 ${i + 1}`,
    description: `임시상세내용 ${i + 1}`,
    createdAt: new Date(1696099999999 + i * 1000 * 60 * 60 * 10).toString(),
  })))();

let cartData: { [key: string]: CartType } = {};

export const handlers = [
  graphql.query(GET_PRODUCTS, (req, res, ctx) => {
    return res(
      ctx.data({
        products: mockProducts,
      })
    );
  }),
  graphql.query(GET_PRODUCT, (req, res, ctx) => {
    const found = mockProducts.find((item) => item.id === req.variables.id);
    if (found) return res(ctx.data(found));
  }),
  graphql.query(GET_CART, (req, res, ctx) => {
    return res(ctx.data(cartData));
  }),
  graphql.mutation(ADD_CART, (req, res, ctx) => {
    const newData = { ...cartData };
    const id = req.variables.id;
    if (newData[id]) {
      newData[id] = {
        ...newData[id],
        amount: (newData[id].amount || 0) + 1,
      };
    } else {
      const found = mockProducts.find((item) => item.id === req.variables.id);
      if (found) {
        newData[id] = {
          ...found,
          amount: 1,
        };
      }
    }
    cartData = newData;
    return res(ctx.data(newData));
  }),
];

이 코드는 MSW (Mock Service Worker)를 사용하여 GraphQL 쿼리와 뮤테이션에 대한 목업 응답을 제공합니다. 간략히 설명하면, 이 코드는 실제 서버 없이 프론트엔드 애플리케이션에서 API를 모방하여 테스트하거나 개발할 수 있게 해줍니다.

  1. 모듈 가져오기
    • graphql을 사용하여 MSW의 GraphQL 함수를 가져옵니다.
    • GET_PRODUCT, GET_PRODUCTS는 GraphQL 쿼리의 이름입니다.
    • ADD_CART, GET_CARTCartType도 GraphQL 관련된 상수와 타입입니다.
  2. 임시 데이터 생성
    • mockProducts는 20개의 임시 상품 데이터를 생성하는 즉시 실행 함수(IIFE)입니다.
    • cartData는 사용자의 장바구니에 담긴 상품을 표현하는 객체입니다. 초기에는 빈 객체로 시작합니다.
  3. 핸들러 정의
    핸들러는 각 쿼리 및 뮤테이션에 대한 응답을 결정하는 함수입니다.
    • GET_PRODUCTS 핸들러
      GET_PRODUCTS 쿼리 요청이 들어오면 mockProducts 데이터를 반환합니다.
    • GET_PRODUCT 핸들러:
      상품 ID를 기반으로 특정 상품을 찾아 반환합니다. 찾을 수 없는 경우 응답이 없습니다.
    • GET_CART 핸들러:
      현재 cartData를 반환하여 장바구니의 현재 상태를 나타냅니다.
    • ADD_CART 뮤테이션 핸들러:
      • 사용자가 장바구니에 상품을 추가하려고 할 때 호출됩니다.
      • 상품 ID를 기반으로 cartData에서 해당 상품을 찾습니다.
      • 상품이 이미 장바구니에 있다면, 상품의 수량을 증가시킵니다.
      • 상품이 장바구니에 없다면, mockProducts에서 해당 상품을 찾아 장바구니에 추가하고 수량을 1로 설정합니다.
      • 업데이트 된 cartData를 반환합니다.
  4. handlers 배열:
    위에서 정의한 핸들러들을 배열로 묶어 내보냅니다. 이 배열은 나중에 MSW를 설정할 때 사용됩니다.

정리

요약하면, 이 코드는 실제 서버와의 통신 없이 클라이언트 사이드에서만 GraphQL 쿼리와 뮤테이션을 테스트하거나 개발할 수 있게 해주는 핸들러들을 제공합니다.

🔆 browser.ts

import { setupWorker } from "msw";
import { handlers } from "./handlers";

export const worker = setupWorker(...handlers);
  • setupWorker: MSW의 주요 함수 중 하나로, 클라이언트 측에서 Service Worker를 설정하고 초기화하는 데 사용됩니다. 이 Service Worker는 네트워크 요청을 가로채서 미리 정의된 응답을 반환합니다.

  • handlers: 이전에 정의했던 mock 핸들러 목록을 가져옵니다.

  • worker: setupWorker 함수를 사용하여 handlers로 Service Worker를 설정하고 이를 worker로 내보냅니다. 이 worker는 애플리케이션에서 mock 서버를 시작하거나 조작하는 데 사용됩니다.

🔆 main.tsx

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./app";
import "./scss/index.scss";
import { BrowserRouter } from "react-router-dom";
import { worker } from "./mocks/browser";
import { RecoilRoot } from "recoil";

if (import.meta.env.DEV) {
  worker.start();
}

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <RecoilRoot>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </RecoilRoot>
  </React.StrictMode>
);
  • worker.start(): 개발 모드(import.meta.env.DEV가 true인 경우)에서만 mock 서버를 시작합니다. 이를 통해 개발 중에만 mock 응답을 받고, 프로덕션 환경에서는 실제 API를 사용하게 됩니다.

  • RecoilRoot: Recoil 상태 관리 라이브러리를 사용하기 위한 루트 제공자입니다. Recoil 상태를 사용하는 모든 컴포넌트는 이 루트 내에 있어야 합니다.

정리

요약하면, 이 코드는 MSW를 설정하여 개발 모드에서만 mock 서버를 사용하고, 애플리케이션의 메인 부분을 초기화하고 렌더링합니다.

4. Recoil

🔆 recoils/cart.ts

import { atom, selectorFamily } from "recoil";

const cartState = atom<Map<string, number>>({
  key: "cartState",
  default: new Map(),
});

export const cartItemSelector = selectorFamily<number | undefined, string>({
  key: "cartItem",
  get:
    (id: string) =>
    ({ get }) => {
      const carts = get(cartState);
      return carts.get(id);
    },
  set:
    (id: string) =>
    ({ get, set }, newValue) => {
      if (typeof newValue === "number") {
        const carts = new Map(get(cartState));
        carts.set(id, newValue);
        console.log(carts);
        set(cartState, carts);
      }
    },
});

해당 Recoil 코드는 장바구니의 상태 관리를 위한 것입니다. 각 코드의 세부 내용과 역할에 대해 설명드리겠습니다.

1. atom

const cartState = atom<Map<string, number>>({
  key: "cartState",
  default: new Map(),
});

atom은 Recoil에서 제공하는 기본 상태 단위입니다. 이 atom은 장바구니의 상태를 관리하며, 각 상품의 ID를 key로, 그 상품의 수량을 value로 가지는 Map을 기본 값으로 사용합니다.

  • key: atom의 고유한 식별자입니다. Recoil에서 모든 atom과 selector는 유니크한 key를 가져야 합니다.

  • default: atom의 기본값입니다. 여기서는 빈 Map 객체로 설정되어 있습니다.

2. selectorFamily

export const cartItemSelector = selectorFamily<number | undefined, string>({
  key: "cartItem",
  get:
    (id: string) =>
    ({ get }) => {
      const carts = get(cartState);
      return carts.get(id);
    },
  set:
    (id: string) =>
    ({ get, set }, newValue) => {
      if (typeof newValue === "number") {
        const carts = new Map(get(cartState));
        carts.set(id, newValue);
        console.log(carts);
        set(cartState, carts);
      }
    },
});

selectorFamily는 파라미터에 따라 다양한 데이터를 반환하는 selector를 생성합니다. 이 경우에는 장바구니 항목의 ID를 받아 해당 항목의 수량을 가져오거나 설정하는 역할을 합니다.

  • key: selector의 고유한 식별자입니다.
  • get: 값을 가져오는 함수입니다. 여기서는 주어진 상품 ID에 대응하는 수량을 가져옵니다.
  • set: 값을 설정하는 함수입니다. 주어진 상품 ID에 대응하는 수량을 설정합니다. 이 함수는 새로운 수량을 받아 cartState atom의 상태를 업데이트합니다. 여기서는 새로운 수량이 숫자인 경우에만 상태를 업데이트하는 조건을 가지고 있습니다.

정리

이 코드는 장바구니의 상태를 관리하기 위한 Recoil의 atom과 selectorFamily를 사용합니다. cartState atom은 전체 장바구니의 상태를 저장하며, cartItemSelector는 특정 상품의 수량을 가져오거나 설정하는 기능을 제공합니다.

5. GraphQL

🔆 QueryClient.ts

import { QueryClient } from "react-query";
// import { getTodos, postTodo } from "../my-api";

type AnyOBJ = { [key: string]: any };

// Create a clientE
export const getClient = (() => {
  let client: QueryClient | null = null;
  return () => {
    if (!client)
      client = new QueryClient({
        defaultOptions: {
          queries: {
            cacheTime: 1000 * 60 * 60 * 24,
            staleTime: 1000 * 60,
            refetchOnMount: false,
            refetchOnReconnect: false,
            refetchOnWindowFocus: false,
          },
        },
      });
    return client;
  };
})();

const BASE_API = "https://fakestoreapi.com";

export const fetcher = async ({
  method,
  path,
  body,
  params,
}: {
  method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
  path: string;
  body?: AnyOBJ;
  params?: AnyOBJ;
}) => {
  try {
    let url = `${BASE_API}${path}`;
    const fetchOptions: RequestInit = {
      method,
      headers: {
        "Content-Type": "application/json",
        "Access-Control-Allow-Origin": BASE_API,
      },
    };
    if (params) {
      const searchParams = new URLSearchParams(params);
      url += "?" + searchParams.toString();
    }

    if (body) fetchOptions.body = JSON.stringify(body);

    const res = await fetch(url, fetchOptions);
    const json = res.json();
    return json;
  } catch (error) {
    console.log(error);
  }
};

export const QueryKeys = {
  PRODUCTS: "PRODUCTS",
};

1. 추가된 Import

import { request } from "graphql-request";
import { DocumentNode } from "graphql";
  • graphql-request: GraphQL API를 간단하게 호출할 수 있도록 도와주는 라이브러리입니다.
  • DocumentNode: GraphQL 쿼리나 뮤테이션을 나타내는 타입입니다.

2. BASE_API 및 BASE_URL의 변경

const BASE_URL = "/";
  • API의 기본 URL이 변경되었습니다. 아마 로컬에서 Mock API를 만들기 때문에 이렇게 바꾸게 되었다.

3. restFetcher 추가

export const restFetcher = async ({ ... }) => { ... };
  • 직전 코드의 fetcher 함수는 restFetcher로 이름이 변경되었습니다.

4. graphqlFetcher 추가

export const graphqlFetcher = (query: DocumentNode, variables = {}) =>
  request(BASE_URL, query, variables);
  • 새로 추가된 함수로, GraphQL API를 호출하기 위한 함수입니다.
  • query: GraphQL 쿼리나 뮤테이션입니다.
  • variables: 쿼리나 뮤테이션에 전달될 변수입니다.

5. QueryKeys의 수정

export const QueryKeys = {
  PRODUCTS: "PRODUCTS",
  CART: "CART",
};
  • QueryKeys 객체에 CART 키가 추가되었습니다.

정리

  • 코드는 react-query를 사용하여 데이터를 가져오기 위한 설정과 유틸리티를 포함하고 있습니다.
  • 기본적으로 QueryClient를 초기화하는 로직은 동일하게 유지됩니다.
  • BASE_API에서 BASE_URL로 변경하고 값도 수정되었습니다.
  • RESTful API를 호출하기 위한 fetcher의 이름이 restFetcher로 변경되었습니다.
  • GraphQL API를 호출하기 위한 새로운 함수 graphqlFetcher가 추가되었습니다.
  • QueryKeys에는 새로운 쿼리 키 CART가 추가되었습니다.

이렇게 보면, 직전 코드와 최근 수정된 코드의 주요 차이는 RESTful API와 GraphQL API 두 가지 방식으로 데이터를 가져올 수 있도록 코드가 확장되었다는 것을 알 수 있습니다.

🔆 graphql/products.ts

import { gql } from "graphql-tag";

export type Product = {
  id: string;
  imageUrl: string;
  price: number;
  title: string;
  description: string;
  createdAt: string;
};

export type Products = {
  products: Product[];
};

export const GET_PRODUCTS = gql`
  query GET_PRODUCTS {
    id
    imageUrl
    price
    title
    description
    createdAt
  }
`;

export const GET_PRODUCT = gql`
  query GET_PRODUCT($id: string) {
    id
    imageUrl
    price
    title
    description
    createdAt
  }
`;

1. gql Import

import { gql } from "graphql-tag";
  • gqlgraphql-tag 라이브러리에서 제공하는 템플릿 리터럴 태그입니다. GraphQL 쿼리 및 뮤테이션 문자열을 파싱하여 GraphQL AST(Abstract Syntax Tree)로 변환합니다. 이렇게 변환된 쿼리는 GraphQL 클라이언트에 의해 사용될 수 있습니다.

2. Product Type

export type Product = {
  id: string;
  imageUrl: string;
  price: number;
  title: string;
  description: string;
  createdAt: string;
};
  • Product 타입은 상품의 상세 정보를 나타냅니다. 각 상품은 ID, 이미지 URL, 가격, 제목, 설명, 생성일자 등의 정보를 가지고 있습니다.

3. Products Type

export type Products = {
  products: Product[];
};
  • Products 타입은 상품 목록을 나타내며, 각 상품의 상세 정보는 앞서 정의한 Product 타입을 사용하여 표현됩니다.

4. GET_PRODUCTS Query

export const GET_PRODUCTS = gql`
  query GET_PRODUCTS {
    products {
      id
      imageUrl
      price
      title
      description
      createdAt
    }
  }
`;
  • GET_PRODUCTS 쿼리는 상품 목록을 조회하는 GraphQL 쿼리입니다. 이 쿼리를 실행하면 각 상품의 ID, 이미지 URL, 가격, 제목, 설명, 생성일자 정보가 반환됩니다.

5. GET_PRODUCT Query

export const GET_PRODUCT = gql`
  query GET_PRODUCT($id: String!) {
    product(id: $id) {
      id
      imageUrl
      price
      title
      description
      createdAt
    }
  }
`;
  • GET_PRODUCT 쿼리는 특정 상품의 상세 정보를 조회하는 GraphQL 쿼리입니다. 이 쿼리는 변수 $id를 필요로 합니다. 제공된 ID에 해당하는 상품의 상세 정보를 반환합니다.

정리

이 코드는 GraphQL을 사용하여 상품 관련 정보를 가져오기 위한 타입과 쿼리를 정의합니다. ProductProducts 타입은 상품 데이터의 구조를 나타내고, GET_PRODUCTSGET_PRODUCT 쿼리는 각각 상품 목록과 특정 상품의 상세 정보를 서버에서 가져오는 데 사용됩니다.

🔆 graphql/cart.ts

1. gql Import

import { gql } from "graphql-tag";
  • gqlgraphql-tag 라이브러리에서 제공하는 템플릿 리터럴 태그입니다. 이를 사용하여 GraphQL 쿼리 및 뮤테이션 문자열을 파싱합니다.

2. CartType

export type CartType = {
  id: string;
  imageUrl: string;
  price: number;
  title: string;
  amount: number;
};
  • CartType 타입은 장바구니 항목의 정보를 나타냅니다. 각 항목은 상품 ID, 이미지 URL, 가격, 제목 및 수량(amount) 등의 정보를 포함하고 있습니다.

3. ADD_CART Mutation

export const ADD_CART = gql`
  mutation ADD_CART($id: string) {
    addToCart(id: $id) {
      id
      imageUrl
      price
      title
      amount
    }
  }
`;
  • ADD_CART 뮤테이션은 장바구니에 상품을 추가하기 위한 GraphQL 뮤테이션입니다. 이 뮤테이션은 변수 $id를 필요로 합니다. 해당 ID의 상품을 장바구니에 추가하면, 장바구니에 추가된 상품의 상세 정보가 반환됩니다.
    (참고: 실제 GraphQL 서버의 스키마에 따라 addToCart와 같은 함수 이름이 정의되어야 합니다.)

4. GET_CART Query

export const GET_CART = gql`
  query GET_CART {
    cart {
      id
      imageUrl
      price
      title
      amount
    }
  }
`;
  • GET_CART 쿼리는 사용자의 장바구니 목록을 조회하는 GraphQL 쿼리입니다. 이 쿼리를 실행하면 사용자의 장바구니에 담긴 모든 상품의 상세 정보가 반환됩니다.
    (참고: 실제 GraphQL 서버의 스키마에 따라 cart와 같은 필드 이름이 정의되어야 합니다.)

정리

이 코드는 GraphQL을 사용하여 장바구니와 관련된 기능을 수행하기 위한 타입과 쿼리/뮤테이션을 정의합니다. CartType은 장바구니 항목의 구조를 나타내고, ADD_CART 뮤테이션은 장바구니에 상품을 추가하는 데 사용되며, GET_CART 쿼리는 장바구니 목록을 가져오는 데 사용됩니다.

6. 변경된 컴포넌트

1. ProductItem 컴포넌트

import { Link } from "react-router-dom";
import { Product } from "../../graphql/products";
import { useMutation } from "react-query";
import { graphqlFetcher } from "../../queryClient";
import { ADD_CART } from "../../graphql/cart";

const ProductItem = ({ imageUrl, price, title, id }: Product) => {
  const { mutate: addCart } = useMutation((id: string) =>
    graphqlFetcher(ADD_CART, { id })
  );
  return (
    <li className="product-item">
      <Link to={`/products/${id}`}>
        <p className="product-item__title">{title}</p>
        <img className="product-item__image" src={imageUrl} />
        <span className="product-item__price">{price}</span>
      </Link>
      <button className="product-item__add-cart" onClick={() => addCart(id)}>
        담기
      </button>
    </li>
  );
};

export default ProductItem;
  • 담기" 버튼을 클릭하면 ADD_CART 뮤테이션을 통해 해당 상품을 장바구니에 추가합니다

2. Cart 컴포넌트

import { useQuery } from "react-query";
import { QueryKeys, graphqlFetcher } from "../../queryClient";
import { CartType, GET_CART } from "../../graphql/cart";
import CartList from "../../components/cart/CartList";

const Cart = () => {
  const { data } = useQuery(QueryKeys.CART, () => graphqlFetcher(GET_CART));

  const cartItems = Object.values(data || {}) as CartType[];
  if (!cartItems.length) return <div>장바구니가 비었어요</div>;

  return <CartList items={cartItems} />;
};

export default Cart;
  • GET_CART 쿼리를 사용하여 장바구니의 상품 목록을 가져옵니다.
  • 장바구니에 아무런 상품이 없으면 "장바구니가 비었어요" 메시지를 표시합니다.
  • 상품이 장바구니에 있으면, 해당 상품 목록을 CartList 컴포넌트에 전달합니다.

3. CartList 컴포넌트

import { CartType } from "../../graphql/cart";
import CartItem from "./CartItem";

const CartList = ({ items }: { items: CartType[] }) => {
  return (
    <ul>
      {items.map((item) => (
        <CartItem {...item} key={item.id} />
      ))}
    </ul>
  );
};

export default CartList;
  • 받아온 상품 목록(items)을 반복하여, 각 상품마다 CartItem 컴포넌트를 렌더링합니다.

4. CartItem 컴포넌트

import { CartType } from "../../graphql/cart";

const CartItem = ({ id, imageUrl, price, title, amount }: CartType) => {
  return (
    <li>
      {id} {imageUrl} {price} {title} {amount}
    </li>
  );
};

export default CartItem;
  • CartType 타입의 props를 받아서 장바구니에 담긴 상품의 정보(상품 ID, 이미지, 가격, 제목, 수량)를 표시합니다.

5. ProductList 컴포넌트

import { useQuery } from "react-query";
import ProductItem from "../../components/products/Item";
import { GET_PRODUCTS, Products } from "../../graphql/products";
import { QueryKeys, graphqlFetcher } from "../../queryClient";

const ProductList = () => {
  const { data } = useQuery<Products>(QueryKeys.PRODUCTS, () =>
    graphqlFetcher(GET_PRODUCTS) as Promise<Products>
  );

  return (
    <div>
      <h2>상품목록</h2>
      <ul className="products">
        {data?.products?.map((product) => (
          <ProductItem {...product} key={product.id} />
        ))}
      </ul>
    </div>
  );
};

export default ProductList;
  • GET_PRODUCTS 쿼리를 사용하여 상품 목록 데이터를 가져옵니다.
  • 상품 목록에 대한 데이터는 useQuery 훅을 통해 가져오며, 결과로 data를 얻습니다.
  • 가져온 데이터가 있을 경우, data.products를 통해 각 상품 데이터를 반복 처리하며 ProductItem 컴포넌트를 사용하여 화면에 렌더링합니다.
  • ProductItem은 개별 상품의 정보(이미지, 제목, 가격 등)를 표시하고, 장바구니에 해당 상품을 추가하는 기능을 제공합니다.
  • graphqlFetcher를 사용하여 GraphQL API를 통해 데이터를 가져옴

6. ProductDetailPage 컴포넌트

import { useQuery } from "react-query";
import { QueryKeys, graphqlFetcher } from "../../queryClient";
import { useParams } from "react-router-dom";
import ProductDetail from "../../components/products/detail";
import { GET_PRODUCT, Product } from "../../graphql/products";

const ProductDetailPage = () => {
  const { id } = useParams();
  const { data } = useQuery<Product>(
    [QueryKeys.PRODUCTS, id],
    () => graphqlFetcher(GET_PRODUCT, { id }) as Promise<Product>
  );

  if (!data) return null;

  return (
    <div>
      <h2>상품상세</h2>
      <ProductDetail item={data} />
    </div>
  );
};

export default ProductDetailPage;
  • useParams 훅을 통해 URL로부터 상품 ID를 가져옵니다.
  • GET_PRODUCT 쿼리와 해당 상품 ID를 사용하여 특정 상품의 상세 정보를 가져옵니다.
  • 상품 상세 정보에 대한 데이터는 useQuery 훅을 통해 가져오며, 결과로 data를 얻습니다.
  • 가져온 데이터가 있을 경우, ProductDetail 컴포넌트에 해당 데이터를 전달하여 화면에 상세 정보를 렌더링합니다.
  • ProductDetail 컴포넌트는 해당 상품의 상세 정보(이미지, 제목, 가격, 설명 등)를 표시합니다
  • graphqlFetcher를 사용하여 GraphQL API를 통해 데이터를 가져옴

정리

  • ProductItem: 상품 목록 페이지에서 개별 상품 항목을 나타내며, 해당 상품을 장바구니에 추가할 수 있습니다.
  • Cart: 사용자의 장바구니 페이지를 나타내며, 장바구니에 담긴 상품들을 보여줍니다.
  • CartList: 장바구니에 담긴 모든 상품들의 목록을 나타내는 컴포넌트입니다.
  • CartItem: 장바구니에 담긴 개별 상품의 정보를 나타내는 컴포넌트입니다.
  • ProductList: 모든 상품의 목록을 화면에 표시하는 컴포넌트입니다. 각 상품은 ProductItem 컴포넌트를 통해 화면에 렌더링됩니다.
  • ProductDetailPage: 특정 상품의 상세 정보를 화면에 표시하는 컴포넌트입니다. 상세 정보는 ProductDetail 컴포넌트를 통해 화면에 표현됩니다.

이렇게 각 컴포넌트는 장바구니와 관련된 특정 기능과 표시 역할을 수행하며, 함께 작동하여 사용자에게 장바구니의 기능을 제공합니다.

profile
필요하다면 공부하는 개발자, 한승준

0개의 댓글