REACT/쇼핑몰 프로젝트

[REACT] React + Typescript + Vite + SCSS로 만드는 쇼핑몰 개인프로젝트 (1/10)

예글 2024. 1. 13. 22:14

😀 개요

전 직장에서 next + typescript로만 개발을 해오고 그마저도 템플릿이 있어 환경설정을 할 필요가 없었다. 또한 리액트 프로젝트를 생성할 때 Create React App으로만 해보고 vite도 한 번도 사용해보지 않아 새로운 환경에서 개발을 해보고 싶었다! 환경설정부터 백엔드 아닌 백엔드까지 전 과정을 혼자서 경험해보고자 이 프로젝트를 시작하게 되었다.

 

 

오랜만에 보는 리액트 로고..

vite는 처음인데.. 생성속도가 아주 빨라서 좋았다.

 

1️⃣ vite로 새로운 프로젝트 생성하기

yarn create vite


? Project name: 프로젝트이름
? Select a framework: (Use arrow keys)
❯ vanilla
  vue
  react
  preact
  lit
  svelte

 

react + typeScript로 선택!

 

2️⃣ vite-plugin-next-react-router 사용하기 (라우터 처리)

1. 설치

yarn add vite-plugin-next-react-router

 

2. vite.config.ts 파일 수정

 

강의를 보면서 따라하다가 _'"vite-plugin-next-react-router"' 모듈에 내보낸 멤버 'reactRouterPlugin'이(가) 없습니다. 대신 '"vite-plugin-next-react-router"에서 reactRouterPlugin 가져오기'를 사용하시겠습니까?ts(2614) 라는 오류가 나서..

(강의를 하신 시점이랑 보고있는 시점이랑 버전이 달라져서 오류가 나는 듯 했다)

 

구글링을 하였더니 마침 나와 같은 오류를 경험한 블로그가 있어서 그 블로그를 참고했다

 

참고 블로그 : https://velog.io/@cjy921004

 

[ React ] vite 프로젝트 생성 & vite-plugin-next-router 사용하기

Vite는 Vue.js 팀에서 개발한 웹 개발 빌드 도구이다. Vite의 주요 목표는 개발 서버와 빌드 시스템을 최적화하여 더 빠른 개발 경험을 제공하는 것이다.Vite는 기존의 번들러(Bundler)와는 다른 접근 방

velog.io

 

3. vite.config.ts  수정

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react-swc";
import withReactRouter from "vite-plugin-next-react-router";

export default defineConfig({
  plugins: [
    react(),
    withReactRouter({
      pageDir: "src/pages",
      extensions: ["js", "jsx", "ts", "tsx"],
      layout: "_layout",
    }),
  ],
});

 

4.  src/pages/ _layout.tsx 파일 생성

import React, { Suspense } from "react";
import { Outlet } from "react-router-dom";

const Layout: React.FC = () => {
  return (
    <div>
      <Suspense fallback={"loading..."}>
        <Outlet />
      </Suspense>
    </div>
  );
};

export default Layout;

 

이렇게 하면 Next.js에서 하던 대로 쉽게 라우팅을 할 수 있다.

 

3️⃣ 상품목록 페이지 만들기

 

1. dummyData 사용하기

 

- 이런 더미데이터가 있는지 몰랐는데 프론트엔드가 백엔드 없이 프로젝트 진행할 때 이용하면 좋은 것 같다!

 

https://fakestoreapi.com/docs

 

Fake Store API

Fake store rest api for your ecommerce or shopping website prototype

fakestoreapi.com

 

2. react-query 사용하기

 

- 드디어 react-query를 사용해보는구나.. 리액트 쿼리 리액트 쿼리 말만 들었지 한 번도 제대로 사용해 본 적이 없는데 이 프로젝트를 통해 완전히 내 것으로 만들고 싶다!

yarn add react-query

 

2-1. src/queryClient.ts 파일 생성

https://tanstack.com/query/v4/docs/react/quick-start

 

Quick Start | TanStack Query Docs

This code snippet very briefly illustrates the 3 core concepts of React Query: If you're looking for a fully functioning example, please have a look at our simple codesandbox example tsx import { useQuery, useMutation, useQueryClient, QueryClient, QueryCli

tanstack.com

import {
  useQuery,
  useMutation,
  useQueryClient,
  QueryClient,
  QueryClientProvider,
} from "react-query";

// Create a client
export const getClient = (() => {
  let client: QueryClient | null = null;

  return () => {
    if (!client) client = new QueryClient({});
    return client;
  };
})();

 

2-2. react-query dev-tool 사용

 

- src/_layout.tsx 에 getClient와 react-query-devtools를 import 해준다

- vite가 업데이트 되어서 main.tsx / app.tsx가 필요없게 되었다. 완전히 지워도 가능!

- 단, index.html에서

<script type="module" src="/src/main.tsx"></script>

이 부분 지워주기!

 

자세한 내용은 

https://www.inflearn.com/questions/868531/caught-error-no-queryclient-set-use-ueryclientprovider-%EC%97%90%EB%9F%AC

 

caught Error: No QueryClient set, use ueryClientProvider 에러 - 인프런

저는 .routes 파일이 생성이 되지않아서 그냥 진행했습니다. 라우터는 잘 작동하더라구요.App에서 elem 대신 <ProductList> 를 감싸줬습니다. ProductList 에 데이터를 불러오는 과정에서 'caught Error: No QueryC

www.inflearn.com

 

여기에서 확인

 

- src/_layout.tsx

import React, { Suspense } from "react";
import { Outlet } from "react-router-dom";
import { QueryClientProvider } from "react-query";
import { ReactQueryDevtools } from "react-query/devtools";
import { getClient } from "../queryClient";

const Layout: React.FC = () => {
  const queryClient = getClient();

  return (
    <QueryClientProvider client={queryClient}>
      <Suspense fallback={"loading..."}>
        <Outlet />
      </Suspense>
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
};

export default Layout;

 

 

- '@tansack/react-query' / '@tansack/react-query/devtools'로 import 하면 오류나니 주의!

 

 

 

2-3. queryClient.ts 메서드, 옵션 설정

import {
  useQuery,
  useMutation,
  useQueryClient,
  QueryClient,
  QueryClientProvider,
} from "react-query";

// any 타입 미리 만들어줌
type AnyOBJ = { [key: string]: any };

// Create a client
export const getClient = (() => {
  let client: QueryClient | null = null;

  return () => {
    if (!client) client = new QueryClient({});
    return client;
  };
})();

// 기본 url
const BASE_URL = "https://fakestoreapi.com";

// async로 요청
export const fetcher = async ({
  method,
  path,
  body,
  params,
}: {
  // 메소드 타입 정의
  method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";

  // url대신 path를 받음
  path: string;

  // post나 put의 경우엔 body가 필요하므로
  body?: AnyOBJ;

  // 파라미터
  params?: AnyOBJ;
}) => {
  try {
    // 기본 url + path
    const url = `${BASE_URL}${path}`;

    // RequestInit은 node에 기본적으로 정의되어 있음
    const fetchOptions: RequestInit = {
      method,
      headers: {
        "Contnet-Type": "application/json",
        "Access-Control-Allow-Origin": BASE_URL,
      },
    };

    // url와 옵션들 요청
    // 메서드와 path를 받아서 완성
    const res = await fetch(url, fetchOptions);

    // 받은 것을 json으로 바꾸기
    const json = await res.json();
    return json;

    // 에러 출력
  } catch (err) {
    console.error(err);
  }
};

// 쿼리 키 만들기
export const QueryKeys = {
  PRODUCTS: "PRODUCTS",
};

 

3. src/products/index.tsx에 상품목록 불러오기 / scss 적용

import { useQuery } from "react-query";
import { QueryKeys, fetcher } from "../../queryClient";
import ProductItem from "../../assets/components/product/item";

const ProductsList = () => {
  // type 정의
  const { data } = useQuery<Product[]>(QueryKeys.PRODUCTS, () =>
    fetcher({
      method: "GET",
      path: "/products",
    })
  );

  console.log(data);

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

export default ProductsList;

 

3-1. 타입은 따로 type.ts 파일 생성해서 적용하기

type Rating = {
  rate: number;
  count: number;
};

type Product = {
  category: string;
  description: string;
  id: number;
  image: string;
  price: number;
  rating: Rating;
  title: string;
};

// interface는 union이 불가

 

4. ProductItem 컴포넌트 생성

const ProductItem = ({
  category,
  description,
  image,
  price,
  rating,
  title,
}: Product) => {
  return (
    <li className="products-item">
      <p className="products-item__category">{category}</p>
      <p className="products-item__title">{title}</p>
      <p className="products-item__description">{description}</p>
      <img className="products-item__image" src={image} />
      <span className="products-item__price">${price}</span>
      <span className="products-item__rate">{rating.rate}</span>
    </li>
  );
};

export default ProductItem;

 

 

상품목록 불러오기 성공!!

 

4️⃣ 상품 상세 페이지로 이동

 

1. <Link> 태그로 감싸서 id를 받아 이동

import { Link } from "react-router-dom";

const ProductItem = ({
  category,
  image,
  price,
  rating,
  title,
  id,
}: Product) => {
  return (
    <li className="products-item">
      <Link to={`/products/${id}`}>
        <p className="products-item__category">{category}</p>
        <p className="products-item__title">{title}</p>
        <img className="products-item__image" src={image} />
        <span className="products-item__price">${price}</span>
        <span className="products-item__rate">{rating.rate}</span>
      </Link>
    </li>
  );
};

export default ProductItem;

 

 

2. src/pages/products/[id].tsx 작성

import { useQuery } from "react-query";
import { useParams } from "react-router-dom";
import { QueryKeys, fetcher } from "../../queryClient";
import ProductDetail from "../../assets/components/product/detail";

const ProductsDetail = () => {
  const { id } = useParams();

  // 데이터 가져오기, type 정의
  const { data } = useQuery<Product>([QueryKeys.PRODUCTS, id], () =>
    fetcher({
      method: "GET",
      path: `/products/${id}`,
    })
  );

  if (!data) return null;

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

export default ProductsDetail;

 

react-query부터 scss까지 한 번도 안 써봤던 걸 써서 애먹은 것도 있었지만,, 새로운 걸 배우니 너무 재밌고 시간도 빨리간다! ㅎㅎ

 

3. queryClient.ts 코드 추가

import {
  useQuery,
  useMutation,
  useQueryClient,
  QueryClient,
  QueryClientProvider,
} from "react-query";

// any 타입 미리 만들어줌
type AnyOBJ = { [key: string]: any };

// Create a client
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;
  };
})();

// 기본 url
const BASE_URL = "https://fakestoreapi.com";

// async로 요청
export const fetcher = async ({
  method,
  path,
  body,
  params,
}: {
  // 메소드 타입 정의
  method: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";

  // url대신 path를 받음
  path: string;

  // post나 put의 경우엔 body가 필요하므로
  body?: AnyOBJ;

  // 파라미터
  params?: AnyOBJ;
}) => {
  try {
    // 기본 url + path
    let url = `${BASE_URL}${path}`;

    // RequestInit은 node에 기본적으로 정의되어 있음
    const fetchOptions: RequestInit = {
      method,
      headers: {
        "Contnet-Type": "application/json",
        "Access-Control-Allow-Origin": BASE_URL,
      },
    };

    // param이 오면
    if (params) {
      const searchParams = new URLSearchParams(params);
      url += "?" + searchParams.toString();
    }

    // body가 오면
    if (body) fetchOptions.body = JSON.stringify(body);

    // url와 옵션들 요청
    // 메서드와 path를 받아서 완성
    const res = await fetch(url, fetchOptions);

    // 받은 것을 json으로 바꾸기
    const json = await res.json();
    return json;

    // 에러 출력
  } catch (err) {
    console.error(err);
  }
};

// 쿼리 키 만들기
export const QueryKeys = {
  PRODUCTS: "PRODUCTS",
};

 

5️⃣ cart 페이지 추가, Gnb 추가

- 장바구니 페이지와 페이지 이동을 할 수 있는 네비게이션 바도 추가해 주었다.

 

 

새로운 걸 배우니까 생소하고 어렵지만 시간 빨리가고 재밌다!! ㅎㅎ