📌 작성자 개발 환경
OS: Mac OS
Tool: Visual Studio Code

3 분 소요

til

Routing

프로젝트의 페이지 라우팅 구성

##

//app.tsx
import { useState } from "react";
import { Routes, useLocation } from "react-router-dom";
import styled from "styled-components";
import { RouteList } from "./RouteList";
import Header from "./share/Header/Header";
import Footer from "./share/Footer";
import useAxiosInterceptor from "./hooks/useAxiosInterceptor";
import useRouteAnimation from "./hooks/useRouteAnimation";

const MainContent = styled.div`
  flex-grow: 1;
`;

function App() {
  const location = useLocation();
  const [displayLocation, setDisplayLocation] = useState(location);
  const [transitionStage, setTransitionStage] = useState("fadeIn");

  useRouteAnimation(
    location,
    displayLocation,
    setDisplayLocation,
    setTransitionStage
  );
  useAxiosInterceptor();

  const hideFooter = location.pathname === "/" || location.pathname === "/auth";

  return (
    <>
      <Header />
      <div
        className={`${transitionStage}`}
        style=
      >
        <MainContent>
          <Routes location={displayLocation}>{RouteList}</Routes>
        </MainContent>
        {!hideFooter && <Footer />}
      </div>
    </>
  );
}

export default App;

코드 분석

MainContent


const MainContent = styled.div`
  flex-grow: 1;
`;

<MainContent>가 부모 컨테이너 내에서 사용 가능한 공간을 모두 확장해서 채우도록 설정 –> 다른 컴포넌트들이 <MainContent>아래로 배치될 때도 이 공간을 유지하게 된다.

예를 들어, <MainContent>는 컨텐츠가 표시되는 주요 영역 <Header> 컴포넌트와 컨텐츠 영역 사이에서 콘텐츠가 확장되는 부분을 담당하게 된다. 이렇게 함으로써, 화면의 크기가 작아질 때도 페이지의 모든 영역이 화면에 표시되도록 한다.

location과 displayLocation 상태


const [displayLocation, setDisplayLocation] = useState(location);

useLocation 훅을 사용하여 현재 경로 정보를 가져오고, useState를 통해 displayLocation 상태를 관리 displayLocation 상태는 페이지 전환에 사용되는 위치 정보를 관리

transitionStage 상태


const [transitionStage, setTransitionStage] = useState("fadeIn");

페이지 전환 애니메이션의 단계를 관리하기 위한 상태 초기 값으로 ‘fadeIn’ 설정 -> 전체 웹 페이지에 애니매이션 적용

const hideFooter = location.pathname === "/" || location.pathname === "/auth";

<MainContent>
  <Routes location={displayLocation}>{RouteList}</Routes>
</MainContent>;
{
  !hideFooter && <Footer />;
}

/, /auth(로그인/회원가입)에는 hideFooter를 통해 Footer가 표시되지 않도록 설정하였다.

Route List


//RouteList.tsx
import { Route } from "react-router-dom";
import Payment from "./pages/order/Payment/Payment";
import Main from "./pages/main/Main";
import Auth from "./pages/users/Auth/Auth";
import Mypage from "./pages/mypage/Mypage";
import ShoppingCart from "./pages/order/ShoppingCart/ShoppingCart";
import StoreList from "./pages/store/StoreList";
import Map from "./pages/map/MapPage";
import SelectStore from "./pages/store/SelectStore";
import OrderComplete from "./pages/order/OrderComplete/OrderComplete";
import StoreDetail from "./pages/store/StoreDetail";
import PrivateRoute from "./PrivateRoute";
import NotFound from "./pages/notfound/NotFound";
import ChatList from "./pages/Chat/ChatList";
import PreferenceProductList from "./pages/preferenceProduct/PreferenceProductList";

export const Routes = [
  { path: "/", element: <Main /> },
  { path: "/select", element: <SelectStore /> },
  { path: "/map", element: <Map /> },
  { path: "/store", element: <StoreList /> },
  { path: "/store/:storeId", element: <StoreDetail /> },
  { path: "/product", element: <PreferenceProductList /> },
  { path: "*", element: <NotFound /> },
  { path: "/auth", element: <Auth />, isAuth: false },
  { path: "/payment", element: <Payment />, isAuth: true },
  { path: "/complete", element: <OrderComplete />, isAuth: true },
  { path: "/mypage", element: <Mypage />, isAuth: true },
  { path: "/cart", element: <ShoppingCart />, isAuth: true },
  { path: "/chatList", element: <ChatList />, isAuth: true, seller: true },
];

export const RouteList = (
  <>
    {Routes.map((route, index) => {
      return route.isAuth === undefined ? (
        <Route key={index} path={route.path} element={route.element} />
      ) : (
        <Route
          element={<PrivateRoute isAuth={route.isAuth} seller={route.seller} />}
        >
          <Route key={index} path={route.path} element={route.element} />
        </Route>
      );
    })}
  </>
);

Route 설정과 Routes 배열


Routes 배열을 정의하고, 각 경로에 대한 설정과 컴포넌트들을 연결하는 부분

export const Routes = [
  { path: "/", element: <Main /> },
  // ... 다른 경로와 컴포넌트들의 설정
];

Routes 배열은 각 경로와 해당 경로에 해당하는 컴포넌트들을 객체 형태로 포함한다. path 속성은 경로를 나타내며, element 속성은 해당 경로로 이동했을 때 렌더링될 컴포넌트를 나타낸다.

RouteList 생성


RouteList는 Routes 배열을 기반으로 실제 Route 컴포넌트를 생성하는 부분

export const RouteList = (
  <>
    {Routes.map((route, index) => {
      return route.isAuth === undefined ? (
        <Route key={index} path={route.path} element={route.element} />
      ) : (
        <Route
          element={<PrivateRoute isAuth={route.isAuth} seller={route.seller} />}
        >
          <Route key={index} path={route.path} element={route.element} />
        </Route>
      );
    })}
  </>
);

Routes 배열을 순회하며 각 경로에 대한 Route 컴포넌트를 생성 isAuth 속성이 정의되어 있는 경우에는 PrivateRoute 컴포넌트로 감싸서, 인증 여부에 따라 보호된 경로 처리를 한다.

PrivateRoute 컴포넌트


인증과 권한에 따른 경로 보호 기능을 구현하는 부분

//PrivateRoute.tsx
import { Navigate, Outlet } from "react-router-dom";
import { LocalStorage } from "./utils/browserStorage";
import { LOCAL_STORAGE_KEY_LIST } from "./assets/constantValue/constantValue";
import { PrivateRouteProps } from "./assets/interface/Router.interface";

function PrivateRoute({ isAuth, seller = false }: PrivateRouteProps) {
  if (seller) {
    const memberRole = LocalStorage.get(LOCAL_STORAGE_KEY_LIST.MemberRole);
    return memberRole === "SELLER" ? <Outlet /> : <Navigate to="/" />;
  }

  const accessToken = LocalStorage.get(LOCAL_STORAGE_KEY_LIST.AccessToken);
  return isAuth === (accessToken !== null) ? <Outlet /> : <Navigate to="/" />;
}

export default PrivateRoute;

PrivateRoute 컴포넌트는 isAuthseller 속성을 받아 인증 여부와 판매자 여부에 따라 경로 보호 기능을 구현하였다.

  • seller 속성이 true인 경우, 로컬 스토리지에서 회원 역할을 확인하여 판매자 역할인 경우 <Outlet />을 반환하고, 아닌 경우 홈 페이지로 이동

  • 그 외의 경우, 로컬 스토리지에서 액세스 토큰을 확인하여 인증 여부를 확인하고, 해당 여부에 따라 경로 보호 여부를 결정

<Outlet />은 하위 경로의 컴포넌트들을 렌더링하는 역할을 한다. <Navigate to="/" />는 경로를 홈 페이지로 이동시키는 역할을 한다.

댓글남기기