관리 메뉴

거니의 velog

(22) 리액트와 상품 API 서버 연동 1 본문

SpringBoot_React 풀스택 프로젝트

(22) 리액트와 상품 API 서버 연동 1

Unlimited00 2024. 3. 5. 12:57

* 상품 API의 경우 파일 데이터가 추가된다는 점을 제외하면 JSON을 이용하는 데이터 처리방식과 유사하다. 하지만 파일 데이터가 추가되기 때문에 처리 과정에 걸리는 시간에 맞는 모달창을 보이는 등의 추가적인 부분이 필요하다. 이 장에서는 컴포넌트의 재사용을 이용해서 이러한 처리를 연습한다.

* 이번 장의 개발 목표는 다음과 같다.

1. 파일이 추가되는 데이터 처리

2. 기존 공통 컴포넌트의 재사용

1. 상품 관련 React-Router 설정

* 개발하려는 상품 기능은 목록 화면에서 새로운 상품을 등록할 수 있고, 조회 화면에서는 수정/삭제 화면으로 이동이 가능하도록 구성해야 한다. 상품 등록 시에는 상품의 이미지들과 함께 추가해서 등록하게 된다.

* 상품 목록 페이지는 상품들의 이미지를 같이 보여주고, 페이지 처리가 가능해야 한다.

* 특정 상품을 선택하면 상품의 조회 페이지가 출력되고 해당 상품의 모든 이미지가 출력되어야 한다.

* 해당 상품의 Modify를 선택하면 상품 수정이 가능하도록 구성한다. 이 때 상품 정보와 같이 새로운 이미지를 추가하거나 기존 이미지를 삭제할 수 있다.

* 상품의 수정/삭제 후에는 모달창에서 수정/삭제 결과를 확인하고 수정 시에는 조회 페이지로, 삭제 시에는 목록 페이지로 이동하게 된다.


* 진행 중인 프로젝트는 리액트의 라우터를 사용해서 개발 구조를 생성해 두었기 때문에 개발은 페이지나 내용물에 해당하는 컴포넌트들을 추가하는 방식으로 개발하게 된다. 페이지와 관련해서 상품 관련된 기능은 pages 폴더 내에 products 폴더를 생성하고 관련된 템플릿 역할을 하는 IndexPage.js를 추가한다.

import React from "react";

const IndexPage = () => {
  return <></>;
};

export default IndexPage;

* React-Router의 설정을 위해서 router 폴더 내에 productsRouter.js 파일을 추가한다.

* producstRouter에는 배열을 반환하는 기능만 구현해 본다.

import React from "react";

const productsRouter = () => {
  return [];
};

export default productsRouter;

* 모든 설정이 시작되는 root.js는 productsRouter.js와 products 폴더의 indexPage 컴포넌트를 사용할 수 있게 추가한다.

import React, { Suspense, lazy } from "react";
import { createBrowserRouter } from "react-router-dom";
import todoRouter from "./todoRouter";
import productsRouter from "./productsRouter";

const Loading = <div>Loading...</div>;
const Main = lazy(() => import("../pages/MainPage"));
const About = lazy(() => import("../pages/AboutPage"));
const TodoIndex = lazy(() => import("../pages/todo/IndexPage"));
const ProductsIndex = lazy(() => import("../pages/products/IndexPage"));

const root = createBrowserRouter([
  {
    path: "",
    element: (
      <Suspense fallback={Loading}>
        <Main />
      </Suspense>
    ),
  },
  {
    path: "about",
    element: (
      <Suspense fallback={Loading}>
        <About />
      </Suspense>
    ),
  },
  {
    path: "todo",
    element: (
      <Suspense fallback={Loading}>
        <TodoIndex />
      </Suspense>
    ),
    children: todoRouter(),
  },
  {
    path: "products",
    element: (
      <Suspense fallback={Loading}>
        <ProductsIndex />
      </Suspense>
    ),
    children: productsRouter(),
  },
]);

export default root;

* components/menus/BasicMenu에서는 브라우저의 상단 메뉴에서 /products/ 경로로 이동하기 위한 링크를 추가한다.

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

const BasicMenu = () => {
  return (
    <nav id="navbar" className=" flex  bg-blue-300">
      <div className="w-4/5 bg-gray-500">
        <ul className="flex p-4 text-white font-bold">
          <li className="pr-6 text-2xl">
            <Link to={"/"}>Main</Link>
          </li>
          <li className="pr-6 text-2xl">
            <Link to={"/about"}>About</Link>
          </li>
          <li className="pr-6 text-2xl">
            <Link to={"/todo/"}>Todo</Link>
          </li>
          <li className="pr-6 text-2xl">
            <Link to={"/products/"}>Products</Link>
          </li>
        </ul>
      </div>

      <div className="w-1/5 flex justify-end bg-orange-300 p-4 font-medium">
        <div className="text-white text-sm m-1 rounded">Login</div>
      </div>
    </nav>
  );
};

export default BasicMenu;

* 여기까지 설정된 프로젝트를 실행하면 아래 화면과 같이 상단 메뉴에 링크를 볼 수 있고, 아직 빈 화면이지만 /products/로 이동이 가능하다(아직 빈 화면만 출력되는 것이 정상).

아무고토 없으니 빈 화면인게 정상임...


(1) 상품 IndexPage

* products의 IndexPage는 <Outlet>을 이용해서 조금 더 세밀한 레이아웃을 지정하도록 작성한다. Todo 예제의 IndexPage를 그대로 이용해도 무방하다.

import React, { useCallback } from "react";
import { Outlet, useNavigate } from "react-router-dom";
import BasicLayout from "../../layouts/BasicLayout";

const IndexPage = () => {
  const navigate = useNavigate();
  const handleClickList = useCallback(() => {
    navigate({ pathname: "list" });
  });
  const handleClickAdd = useCallback(() => {
    navigate({ pathname: "add" });
  });

  return (
    <BasicLayout>
      <div className="text-black font-extrabold -mt-10">Products Menus</div>
      <div className="w-full flex m-2 p-2 ">
        <div
          className="text-xl m-1 p-2  w-20 font-extrabold text-center underline"
          onClick={handleClickList}
        >
          LIST
        </div>
        <div
          className="text-xl m-1 p-2 w-20 font-extrabold  text-center underline"
          onClick={handleClickAdd}
        >
          ADD
        </div>
      </div>
      <div className="flex flex-wrap w-full ">
        <Outlet />
      </div>
    </BasicLayout>
  );
};

export default IndexPage;

* 브라우저에서는 /product/ 경로에서 아래와 같이 ADD 링크가 있는 화면을 확인할 수 있고, LIST나 ADD 버튼을 클릭하는 경우 /products/list와 같이 /product/ 경로로 이동하는 것을 볼 수 있다.


(2) ListPage

* 먼저 상품의 목록을 보여주는 pages/products 폴더 내에 ListPage를 추가하고 라우팅 설정을 진행한다.

import React from "react";

const ListPage = () => {
  return (
    <div className="p-4 w-full bg-white">
      <div className="text-3xl font-extrabold">Products List Page</div>
    </div>
  );
};

export default ListPage;

* 만들어진 ListPage는 router/productsRouter.js에서는 /products/ 경로를 호출할 때 자동으로 /products/list로 이동하고 ListPage를 보이도록 설정한다.

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

const productsRouter = () => {
  const Loading = <div>Loading....</div>;
  const ProductsList = lazy(() => import("../pages/products/ListPage"));
  return [
    {
      path: "list",
      element: (
        <Suspense fallback={Loading}>
          <ProductsList />
        </Suspense>
      ),
    },
    {
      path: "",
      element: <Navigate replace to="/products/list" />,
    },
  ];
};

export default productsRouter;

* 화면에서는 Products 메뉴에서 /products 로 이동했을 때 자동으로 /products/list로 이동하게 된다.