관리 메뉴

거니의 velog

(4) React-Router 2 본문

SpringBoot_React 풀스택 프로젝트

(4) React-Router 2

Unlimited00 2024. 2. 26. 19:25

(6) 하위 경로의 설정과 <Outlet>

* 일반적으로 웹에서는 특정한 모듈에 접근했을 때의 기본적인 화면은 대시보드(Dashboard)와 같은 여러 상태를 한 눈에 볼 수 있는 화면이나 목록 페이지를 보여주는 것이다. 예제에서는 '/todo/list' 대신에 '/todo/' 경로의 링크만을 남겨두었는데, 이렇게 하는 이유는 모듈의 이름 자체가 하나의 경로가 된다는 원칙을 세우고 새로운 모듈들의 개발 방식을 통일하기 위함이다.
* 프로젝트의 pages 폴더에 todo 폴더를 생성해 두고 위의 경로에는 존재하지 않는 IndexPage.js 파일을 추가한다. IndexPage.js는 BasicLayout을 이용해서 전체적인 레이아웃이 적용된 가운데서 다시 Todo와 관련된 메뉴나 화면을 구성한다.
* 개발에 앞서 BasicLayout에서 영역을 알아보기 위해 설정한 padding이나 flex 관련 설정을 조정한다.

import React from "react";
import BasicMenu from "../components/menus/BasicMenu";

const BasicLayout = ({ children }) => {
  return (
    <>
      {/* 기존 헤더 대신 BasicMenu */}
      <BasicMenu />

      {/* space-y-1 변경, md:space-x-1 변경 */}
      <div className="bg-white my-5 w-full flex flex-col space-y-1 md:flex-row md:space-x-1 md:space-y-0">
        {/* md:w-4/5 변경, py-5 변경 */}
        <main className="bg-sky-300 md:w-4/5 lg:w-3/4 px-5 py-5">
          {children}
        </main>
        {/* md:w-1/5 변경, flex 추가, py-5 변경 */}
        <aside className="bg-green-300 md:w-1/5 lg:w-1/4 px-5 flex py-5">
          <h2 className="text-2xl md:text-4xl">Sidebar</h2>
        </aside>
      </div>
    </>
  );
};

export default BasicLayout;

* todo 폴더의 IndexPage에서는 '/todo/' 이하 메뉴에서 필요한 하위 메뉴를 보여주고 하위 페이지들의 화면을 보여주는 <Outlet> 설정을 추가한다.

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

const IndexPage = () => {
  return (
    <BasicLayout>
      <div className="w-full flex m-2 p-2 ">
        <div className="text-xl m-1 p-2 w-20 font-extrabold text-center underline">
          LIST
        </div>
        <div className="text-xl m-1 p-2 w-20 font-extrabold text-center underline">
          ADD
        </div>
      </div>
      <div className="flex flex-wrap w-full">
        <Outlet />
      </div>
    </BasicLayout>
  );
};

export default IndexPage;

* IndexPage를 보면 React-Router의 <Outlet>이 적용된 것을 볼 수 있다. <Outlet>은 중첩적으로 라우팅이 적용될 때 기존 컴포넌트의 구조를 유지할 수 있는데 이를 통해서 '/todo/list'와 같이 하위 경로에 맞는 페이지 컴포넌트를 제작할 때 IndexPage의 구조가 유지될 수 있게 된다. <Outlet>을 활용하면 좀 더 세밀한 레이아웃의 재사용 단위를 구성할 수 있다.
* IndexPage에는 LIST, ADD와 같이 링크를 위한 <div>들을 생성해 두었다. 이 링크는 IndexPage를 사용하는 모든 컴포넌트에 공통으로 사용할 수 있다. Todo 기능과 관련된 라우팅 설정을 위해 router/root.js에 '/todo' 관련 경로를 추가한다.

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

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 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>
    ),
  },
]);

export default root;

* 브라우저에서 확인해 보면 BasicLayout의 메뉴를 통해서 '/todo/;로 접근할 때 todo/IndexPage 컴포넌트가 출력되는 것을 확인할 수 있다.


(7) todo/list 경로 처리

* <Outlet>을 활용하면 중첩적인 라우팅 처리 설정 시 레이아웃을 유지할 수 있는데 이를 확인하기 위해서 'todo/list'와 같이 중첩적인 경로를 처리해 보겠다. 먼저, 프로젝트의 pages/todo 폴더에 ListPage.js 파일을 추가한다.

import React from "react";

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

export default ListPage;

* ListPage 컴포넌트는 별도의 레이아웃에 대한 처리 없이 필요한 내용만으로 작성된 것을 확인할 수 있다. 이런 방식으로 실제 페이지들을 구성하면 공통적인 메뉴나 레이아웃 없이 코드를 독립적으로 제작할 수 있게 된다.



[1] React-Router의 중첩 라우팅
* React-Router는 하나의 경로 설정에서 children 속성을 이용해서 하위로 중첩적인 경로를 지정할 수 있다. router/root.js 파일을 아래와 같이 수정한다.

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

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 TodoList = lazy(() => import("../pages/todo/ListPage"));

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: [
      {
        path: "list",
        element: (
          <Suspense fallback={Loading}>
            <TodoList />
          </Suspense>
        ),
      },
    ],
  },
]);

export default root;

* 위의 설정을 적용하면 브라우저에서 'todo/list'로 접근할 경우 <Outlet> 부분이 ListPage 컴포넌트로 처리되는 것을 확인할 수 있다.


(8) 중첩 라우팅의 분리와 리다이렉션(Redirection)

* 위와 같이 하나의 라우팅 설정에 children 속성을 이용해서 중첩적인 라우팅 설정을 적용할 수 있지만 페이지가 많아지면 root.js 파일이 너무 복잡해지는 단점이 있다. 그렇기 때문에 이럴 떄는 별도의 함수에 children 속성값에 해당하는 설정을 반환하는 방식이 좀 더 알아보기 수월하다.
* router 폴더에 todoRouter.js 파일을 추가하고 '/todo/' 하위의 설정들을 반환하도록 함수를 작성한다(일반적으로 리액트에서 파일의 이름은 컴포넌트를 제외하면 소문자로 작성한다).

import React, { Suspense, lazy } from "react";

const Loading = <div>Loading...</div>;
const TodoList = lazy(() => import("../pages/todo/ListPage"));

const todoRouter = () => {
  return [
    {
      path: "list",
      element: (
        <Suspense fallback={Loading}>
          <TodoList />
        </Suspense>
      ),
    },
  ];
};

export default todoRouter;

* todoRouter.js 파일은 기존의 root.js 파일의 설정 일부를 별도의 파일로 분리하고 설정을 반환하도록 한 것이다. 기존의 '/todo/' 와 관련된 children 설정을 담고 있는 라우팅 설정은 todoRouter를 호출하는 형태로 수정한다.

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

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 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(),
  },
]);

export default root;

* 수정된 부분을 보면 '/todo/' 경로와 관련된 설정은 todoRouter() 를 이용하도록 변경된 것을 볼 수 있다.



[1] 리다이렉션 처리
* todoRouter 내에, 경로에 대한 설정을 추가하긴 하지만 '/todo/' 경로로 접근하는 경우 자동으로 '/todo/list'를 바라볼 수 있도록 처리해 주면 <Outlet> 설정을 유지한 상태에서 사용이 가능하다. React-Router에는 <Nevigate>의 replace 속성을 이용해서 특정 경로로 진입 시에 자동으로 리다이렉션을 처리할 수 있다.
* todoRouter.js에서 '/todo/' 이하의 경로가 지정되지 않았을 때 동작하는 빈 경로의 설정을 아래와 같이 추가한다.

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

const Loading = <div>Loading...</div>;
const TodoList = lazy(() => import("../pages/todo/ListPage"));

const todoRouter = () => {
  return [
    {
      path: "list",
      element: (
        <Suspense fallback={Loading}>
          <TodoList />
        </Suspense>
      ),
    },
    {
      path: "",
      element: <Navigate replace to="list" />,
    },
  ];
};

export default todoRouter;

* 추가된 설정으로 인해 브라우저에서 'Todo' 메뉴를 선택하거나 '/todo/' 경로를 호출하는 경우 자동으로 '/todo/list'로 이동되는 것을 확인할 수 있다.


(9) URL Params 사용하기

* 예제가 완성된다면 목록 페이지에서 조회 페이지로 이동할 때 경로가 변경된다. 최근 웹에서는 주로 경로의 마지막에 고유한 식별번호를 사용하는 것이 일반적이다. 예제에서는 특정한 번호의 Todo를 조회할 경우 '/todo/read/33'과 같은 경로로 이동하는 방식을 적용한다. React-Router는 경로에 필요한 데이터가 있을 때는 ' : '을 활용한다.



[1] 페이지 추가
* pages/todo 폴더 내에 조회할 때 사용할 ReadPage.js 파일을 추가한다.

import React from "react";

const ReadPage = () => {
  return (
    <div className="text-3xl font-extrabold">Todo Read Page Component</div>
  );
};

export default ReadPage;

*  router/todoRouter.js 에는 ReadPage 컴포넌트에 대한 경로 설정을 추가한다.

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

const Loading = <div>Loading...</div>;
const TodoList = lazy(() => import("../pages/todo/ListPage"));
const TodoRead = lazy(() => import("../pages/todo/ReadPage"));

const todoRouter = () => {
  return [
    {
      path: "list",
      element: (
        <Suspense fallback={Loading}>
          <TodoList />
        </Suspense>
      ),
    },
    {
      path: "",
      element: <Navigate replace to="list" />,
    },
    {
      path: "read/:tno",
      element: (
        <Suspense fallback={Loading}>
          <TodoRead />
        </Suspense>
      ),
    },
  ];
};

export default todoRouter;

* 추가된 'read/:tno'의 부분에서 ' : '은 경로의 일부를 변수로 사용하기 위한 설정으로 브라우저에서 특정한 번호를 조회하는 용도로 사용한다. 브라우저에서 '/todo/read/33'과 같은 경로를 호출하면 ReadPage 컴포넌트가 실행되는 것을 확인할 수 있다(마지막의 번호는 식별자의 의미를 가진다).


 

'SpringBoot_React 풀스택 프로젝트' 카테고리의 다른 글

(6) 스프링 부트와 API 서버 1  (0) 2024.02.26
(5) React-Router 3  (0) 2024.02.26
(3) React-Router 1  (0) 2024.02.26
(2) 개발 환경설정  (0) 2024.02.26
(1) 들어가며  (1) 2024.02.26