[React] 이벤트 앱 만들기 연습: 리액트 라우터 사용하기

summereuna🐥·2023년 6월 9일
0

React JS

목록 보기
62/69

앱이 복잡해질 수록 웹사이트/웹 앱의 특정 부분에 바로 링크되면 좋다.
웹사이트 특정 부분 방문 시 그 부분을 로딩하는 URL을 제공하는 것이다.
SPA 라우팅이 바로 그 부분에 사용된다.
리액트 라우터를 사용하면 매번 백엔드에서 새로운 페이지를 가져오지 않고 다양한 URL로 원하는 페이지를 방문할 수 있다.

React-Router 사용하여 싱글페이지 리액트 앱에서 멀티 페이지 느낌 작업하기

  • 리액트 라우터가 무엇이고 왜 사용할까? (What & Why?)
  • React-Router 사용 방법 (Using React-Router)
  • 데이터를 가져오고 보내는 방법 (Data Fetching & Submission)

✅ 1. 리액트 라우터가 무엇이고 왜 사용할까? (What & Why?)


라우팅을 이해하려면 웹이 전반적으로 어떻게 작동하는지 이해해야 한다.

  • 웹사이트를 방문하면 일반적으로 도메인 이름 뒤에 경로를 첨부할 수 있다.
    https://blog.naver.com/userId 처럼 말이다. 이 url은 해당 유저의 네이버 블로그 페이지를 로딩한다.
    다른 유저의 아이디를 입력하면 그 유저의 블로그 페이지가 로딩된다.
    이렇게 웹사이트에 표시되는 콘텐츠가 변경된다.

  • 이것이 라우팅이 하는 일이다.
    url 경로가 다르면 다른 콘텐츠가 화면에 로딩된다.

Multi-Page Routing: 콘텐츠 마다 다른 HTML


현재까지는 다른 콘텐츠에 대해 다른 HTML을 로딩하는 방식으로 구현했다.
이 방법은 단점이 몇 가지 있다.

  • 항상 새로운 콘텐츠를 가져와야 한다.
  • 새로운 HTTP 요청을 전송하고 새로운 응답을 받는 과정에서 사용자의 흐름이 중단 될 수 있다.
  • 그러면 지연이 발생하고 웹사이트가 느려질 수 있다.
  • 그래서 사용자 경험이 저하 되는 결과를 낳을 수 있다.

SPA(Single Page Application)


그래서 더 복잡한 사용자 인터페이스를 구축할 때 SPA(싱글 페이지 어플리케이션)를 사용한다.

그러면 최초 HTML 요청을 하나만 전송하는데, html 파일과 추가로 많은 JavaScript가 다운로드되고, 클라이언트에서 실행되는 추가 JavaScript 코드는 사용자가 화면에서 보는 것들을 실제로 조절하게 된다.

  • "페이지가 하난데 url 경로를 변경하지 못하는게 아닌가요?" 라고 생각할 수 있지만, 클라이언트 측 리액트 코드를 추가하여 현재 사용중인 url에 따라 url이 변경될 때마다 작동하여 다른 콘텐츠를 화면에 표시할 수 있다.
  • 따라서 백엔드로부터 새로운 HTML 파일을 로딩하지 않고도 약간의 클라이언트 측 코드를 추가하여 URL 변경 시 리액트 컴포넌트를 로딩할 수 있다.
  • 그렇게 여전히 SPA에 있으면서도 다양한 URL과 라우팅을 지원할 수 있게 된다.

✅ 2. React-Router 사용 방법 (Using React-Router)


리액트 라우터 돔 설치

npm i react-router-dom

사용 단계

  1. 지원하려는 라우트 정의
    지원하려는 URL과 경로, 다양한 경로에 대해 어떤 컴포넌트가 로딩되어야 하는지 정의

  2. 라우터 활성화하여 1에서 정의한 라우트를 로딩

  3. 로딩하려는 모든 컴포넌트들이 있는지 확인하고 페이지들 간에 이동할 수단 제공했는지 확인

위 세 단계가 잘 되었다면 사용자들은 다양한 페이지들 사이를 매끄럽게 이동할 수 있다.

이제 단계별로 살펴보자.


1. 지원하려는 라우트 정의: createBrowserRouter([{ 라우트 객체 }])


  1. 루트 컴포넌트인 App.js에서 react-router-dom 패키지로부터 createBrowserRouter 가져오기
    import { createBrowserRouter } from "react-router-dom";

  2. 라우터 정의

  • createBrowserRouter([{}, {}, {}, ...]);
    createBrowserRouter로 객체로 된 어레이를 보내는데, 각각의 객체는 각각 하나의 라우트를 나타낸다.

  • 라우트를 구성하는 몇 가지 프로퍼티가 있다.
    createBrowserRouter([{ path: "/", element: <Home/> }]);

    • path 프로퍼티: 라우트가 활성화되어야 하는 경로를 추가한다.
      도메인 뒤에 붙은 경로가 바로 path이다. (예: "/", "/userId")
    • element(요소) 프로퍼티: 패스로 로딩될 컴포넌트를 뱉는 JSX코드를 정의하면 된다.
  • /src/pages pages 폴더를 프로젝트에 추가하여 라우터에 의해 페이지로서 로딩될 컴포넌트를 담자.
    폴더이름은 /pages, /components, /routes 뭘로 하든 상관 없지만 직관적이게 /pages를 많이 쓰나보다.

2. 라우터 활성화하여 1에서 정의한 라우트 로딩: RouterProvider


  1. 라우터를 사용하기 위해서는 createBrowserRouter함수의 리턴 값을 변수/상수에 저장해야 한다.
    const router = createBrowserRouter([{ path: "/", element: <Home /> }]);

  2. 라우터 활성화 하기

  • 먼저 react-router-dom으로 부터 RouterProvider를 가져와 App 컴포넌트의 JSX코드에 사용한다.
    import { RouterProvider, createBrowserRouter } from "react-router-dom";
  • RouterProvider의 router 프로퍼티로 생성해둔 라우터를 보낸다.
function App() {
  return <RouterProvider router={router} />;
  //이렇게 router를 활성화 한다. 이제 router는 우리 url을 관찰하녀 현재 활성인 경로가 무엇인지 확인하여 element를 로딩한다.
}

👉 코드

import { RouterProvider, createBrowserRouter } from "react-router-dom";
import Home from "./pages/Home";

const router = createBrowserRouter([{ path: "/", element: <Home /> }]);

function App() {
  return <RouterProvider router={router} />;
}

export default App;

(참고) React Router 6.4 버전 이하에서는 아래 처럼 사용하기도 했다.
나도 작년엔 이거 사용했었음..

import {
  Route,
  RouterProvider,
  createBrowserRouter,
  createRoutesFromElements,
} from "react-router-dom";
import Home from "./pages/Home";
import Products from "./pages/Products";
//
const routeDefinitions = createRoutesFromElements(
  <Route>
    <Route path="/" element={<Home />} />
    <Route path="/products" element={<Products />} />
  </Route>
);
//
const router = createBrowserRouter(routeDefinitions);
//
function App() {
  return <RouterProvider router={router} />;
}
//
export default App;

3. 로딩하려는 모든 컴포넌트들이 있는지 확인하고 페이지들 간에 이동할 수단 제공했는지 확인


📝 Link로 링크 보내기


앵커(a)로 링크 보내기: 성능 악화 😵

<a href="/products">go to list of products.</a>로 링크를 걸어 페이지를 이동할 수도 있다.

  • 하지만 이렇게 하면 링크 클릭 시 서버에 새로운 HTTP 요청을 전송한다.
    그리고 서버는 이 SPA를 구성하는 싱글 HTML 페이지를 제공하는데 모든 JavaScript 코드를 다시 로딩하고 리액트 앱 전체를 다시 로딩하고 재시작한다.
  • 이런 불필요한 작업은 사이트 성능에도 영향을 미치기 때문에 되도록 피해야 한다.

Link로 링크 보내기: 😇👍

<Link to="/products">링크: 프로덕트로 이동!</Link>

  • Link 컴포넌트는 배후에서 앵커(a) 요소를 렌더링하지만, 기본적으로 앵커 요소에 대한 클릭을 관찰하고, 링크 클릭 시 HTTP 요청을 전송하는 브라우저 기본 설정을 막아준다.
  • 단순히 라우트 정의를 확인하여 그에 맞춰 페이지를 업데이트하고 그 패스에 맞는 콘텐츠를 로딩한다.
import { Link } from "react-router-dom";

const Home = () => {
  return (
    <>
      <h1>My Home Page</h1>
      <Link to="/products">링크: 프로덕트로 이동!</Link>
    </>
  );
};

export default Home;

앵커와 링크의 차이를 보시라!


📝 레이아웃 및 중첩된 라우트: 네비게이션, 레이아웃 페이지, 중첩 라우트, Outlet으로 자녀 라우트 마킹


1. 페이지 이동 쉽도록 네비게이션 만들기

상단에 네비게이션 바를 추가하자. 대부분의 웹사이트는 이동이 쉽도록 네비게이션 바가 있다. ㅇㅇ

/components 폴더를 만들어 네비게이션 컴포넌트를 만들자.

네비게이션을 어디에서 렌더링해야 할까가 문제이다.
RouterProvider 밖에서 네비게이션을 렌더링한다면 Link를 사용할 수가 없다.
그렇기 때문에 가장 상단에 네비게이션을 렌더링하는 랩퍼 컴포넌트를 두고 그 자식으로 홈, 프로덕트 라우트를 중첩해서 사용해 보자.

2. 레이아웃 페이지 만들기

/pages/Root.js를 만들고 네비게이션을 가져온다.

import MainNavigation from "../components/MainNavigation";

const RootLayout = () => {
  return <MainNavigation />;
};

export default RootLayout;

3. 라우트 중첩하기

"/"를 패스로 갖고 RootLayout을 요소로 갖는 새 라우트 객체를 만들고, children 프로퍼티에 홈, 프로덕트 라우트를 중첩하여 배열로 넣는다.

이렇게 홈, 프로덕트 라우트를 루트레이아웃 라우트의 자녀 라우트로 넣어주면, 부모 라우트인 루트레이아웃은 wrapper 역할을 하게된다.

import { RouterProvider, createBrowserRouter } from "react-router-dom";
import Home from "./pages/Home";
import Products from "./pages/Products";
import RootLayout from "./pages/Root";

const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    children: [
      { path: "/", element: <Home /> },
      { path: "/products", element: <Products /> },
    ],
  },
]);

function App() {
  return <RouterProvider router={router} />;
}

export default App;

4. Outlet 컴포넌트로 중첩된 자녀 라우트 컴포넌트 위치 마킹하기

Outlet 컴포넌트로 자녀 라우트를 렌더링할 장소를 표시할 수 있다.

import { Outlet } from "react-router-dom";
import MainNavigation from "../components/MainNavigation";

const RootLayout = () => {
  return (
    <>
      <MainNavigation />
      <Outlet />
    </>
  );
};

export default RootLayout;
  • 네비게이션이 있고, 그 아래 Outlet 부분에서 자녀 라우트가 렌더링할 컴포넌트인 <Home/>, <Products/>가 렌더링 된다.
  • 이렇게하면 children 어레이에 추가하는 모든 페이지에 네비게이션이 위치할 수 있다.

이렇게 레이아웃 역할을 하는 루트 라우트를 만드는 것은 리액트 라우터를 사용할 때 아주 표준적이고 정상적인 방법이다.

더 복잡한 페이지의 경우, 다수의 루트 라우트를 만들고 각 루트 라우트 별로 children을 추가하여 자식들을 감싸는 다른 레이아웃을 가질 수 있다. 이것이 장점 ㅇㅇ!


📝 errorElement로 오류 페이지 표시하기


설정하지 않은 페이지 방문 시 이렇게 오류 페이지가 뜨는데, 이 기본 오류 메시지는 react-router-dom 패키지가 생성한 메시지이다.

방문자들이 방문하지 말아야할 페이지나 존재하지 않는 페이지로 접근하는 것을 방지하기 위해, 기본 오류 페이지를 준비해 보자.

1. 에러 페이지 만들기

먼저 /pages/Error.js 에러페이지를 만든다.

import MainNavigation from "../components/MainNavigation";

function ErrorPage() {
  return (
    <>
      <MainNavigation />
      <main>
        <h1>에러 발생!</h1>
        <p>페이지를 찾을 수 없습니다.</p>
      </main>
    </>
  );
}

export default ErrorPage;

2. 에러 메시지를 뱉을 라우트에 errorElement 프로퍼티 추가하기

errorElement 프로퍼티 추가하여 에러 발생시 뱉을 에러 페이지 추가할 수 있다.

//...
import ErrorPage from "./pages/Error";

const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    //errorElement 프로퍼티 추가하여 에러 발생시 뱉을 에러 페이지 추가할 수 있다.
    errorElement: <ErrorPage />,
    children: [
      { path: "/", element: <HomePage /> },
      { path: "/products", element: <ProductsPage /> },
    ],
  },
]);

//...
  • 잘못된 url 접근 시 에러 페이지가 표시된다.


Link 대신 NavLink를 사용하면 네비게이션에서 현재 있는 url, 즉 활성 상태인 링크를 하이라이팅 할수 있는 active 클래스를 사용할 수 있다.

1. css에서 앵커에 hover시 active 클래스가 작동하도록 스타일 설정하기

Link 컴포넌트는 일반적인 앵커 요소를 렌더링하기 때문에 a앵커 태그에 스타일을 추가하면 된다.

.list a {
  text-decoration: none;
  color: var(--color-primary-400);
}

.list a:hover,
.list a.active {
  color: var(--color-primary-800);
  text-decoration: underline;
}

NavLink에서는 className 프로퍼티가 문자열을 받는 일반적인 className 프로퍼티가 아닌, 함수를 받는 className 프로퍼티이다.

  • 그 함수는 자동으로 객체{}를 받는데, 거기에 isActive 프로퍼티를 할당하여 isActive인지에 따라 앵커 태그에 추가되어야 하는 클래스 이름을 리턴하여 하이라이팅을 줄 수 있다.
<NavLink
  className={({ isActive }) => (isActive ? classes.active : undefined )}
  to="/"> Home
</NavLink>

코드

import { NavLink } from "react-router-dom";
import classes from "./MainNavigation.module.css";

const MainNavigation = () => {
  return (
    <header className={classes.header}>
      <nav>
        <ul className={classes.list}>
          <li>
            <NavLink
              className={({ isActive }) => (isActive ? classes.active : undefined)}
              to="/"
            >
              Home
            </NavLink>
          </li>
          <li>
            <NavLink
              className={({ isActive }) => (isActive ? classes.active : undefined)}
              to="/products"
            >
              Products
            </NavLink>
          </li>
        </ul>
      </nav>
    </header>
  );
};

export default MainNavigation;

  • NavLink는 현재 활성인 라우트의 경로가 NavLink의 경로로 시작하는지 확인한다.
    중첩된 자녀 라우트에 있는 경우에도 링크를 활성으로 취급할 수 있게 하기 위해서 이런 동작이 존재한다.

(참고) 인라인 스타일로 isActive 인지 확인하여 클래스 추가할 수도 있다.
하지만 굳이..! 😇

3. end 프로퍼티 설정하여 경로 오류 해결하기

"/" 로 시작하는 모든 라우트에 대해 활성상태가 되어버리는 것을 방지하기 위해서 end 프로퍼티를 사용하자.

  • end 프로퍼티를 추가하면 현재 활성인 라우트의 URL 뒤가 이 경로로 끝나면 이 링크만 활성으로 간주한다는 뜻이다.
    • "/"만 활성 true, "/abc"는 활성 false
<NavLink
  className={({ isActive }) =>isActive ? classes.active : undefined}
  to="/"
  end
  >

📝 프로그램적으로 네비게이션 하기: useNavigate()


  1. 사용자가 링크 클릭을 통해 페이지 간에 이동하는 방법
  2. 프로그램적으로 네비게이션 하기: 강제 라우팅
    • 폼 제출 시 페이지 이동
    • 타이머 만료 시 페이지 이동

프로그램적으로 네비게이션 하기 위해 useNavigate() 훅 사용하기

사용방법

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

const HomePage = () => {
  const navigate = useNavigate();
  
  //네비게이션 동작 트리거 할 수 있고, 프로그램적으로 코드 안에서 다른 라우트로 전환할 수 있음
  const navigateHandler = () => {
    //navigate 함수 호출하여 이동할 라우트 작성
    navigate("/products");
  };
  return (
    <>
      <h1>My Home Page</h1>
      <button onClick={navigateHandler}>Go To Products</button>
    </>
  );
};

export default HomePage;

보통 useNavigate 훅을 사용하여 버튼 클릭으로는 페이지를 이동하지는 않지만, 사용법을 간단히 익히기 위해 버튼을 클릭 시 함수를 트리거하여 useNavigate로 "/products" 페이지로 넘어가게 했다.


📝 동적 라우트 정의하고 사용하기: :id, useParams()

제품 페이지로 가면 보통 많은 제품 리스트(ul)가 있다.

const ProductsPage = () => {
  return (
    <>
      <h1>Products</h1>;
      <ul>
        <li>P1</li>
        <li>P2</li>
        <li>P3</li>
      </ul>
    </>
  );
};

export default ProductsPage;

그리고 다양한 제품에 대한 별도의 세부 정보 페이지 로딩할 수 있게 링크를 걸어둔다.(li)
보통 제품 상세페이지에 대한 컴포넌트에 프로덕트 정보를 매핑하여 사용하게 되는데 이럴 때 경로(path)는 어떻게 해야 할까?

1. 제품 상세 페이지를 만든다.

📍src/pages/ProductDetail.js

const ProductDetail = () => {
  return (
    <>
      <h1>Product Detail</h1>
    </>
  );
};

export default ProductDetail;

2. 라우트 설정

import { RouterProvider, createBrowserRouter } from "react-router-dom";
import HomePage from "./pages/Home";
import ProductsPage from "./pages/Products";
import ProductDetail from "./pages/ProductDetail";
import RootLayout from "./pages/Root";
import ErrorPage from "./pages/Error";

const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    errorElement: <ErrorPage />,
    children: [
      { path: "/", element: <HomePage /> },
      { path: "/products", element: <ProductsPage /> },
      // 콜론은 경로의 이 부분이 dynamic 하다는 사실을 리액트 라우터 돔에게 알려준다.
      { path: "/products/:id", element: <ProductDetail /> },
    ],
  },
]);

function App() {
  return <RouterProvider router={router} />;
}

export default App;

리액트 라우터 돔은 역동적 path 세그먼트, 경로 파라미터를 지원한다.

  • 콜론(:)은 경로의 이 부분이 dynamic 하다는 사실을 리액트 라우터 돔에게 알려준다.

3. useParams() 으로 제품의 실제 :id 값 가져오기

이제 어떤 제품에 대한 ProductDetail 페이지가 로딩되었는지 알기 위해 useParams()을 사용하여 플레이스 홀더, 즉 :id 대신 사용된 실제값을 가져 올 수 있다.

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

const ProductDetail = () => {
  
  const params = useParams();
  const id = params.id;
  
  return (
    <>
      <h1>Product Detail</h1>
      <p>{id}</p>
    </>
  );
};

export default ProductDetail;
  • const params = useParams();
    이 params 객체는 라우트 정의에서 프로퍼티로 정의한 모든 역동적 경로 세그먼트가 담긴 JS 객체이다.
    라우트 설정 시, 콜론의 뒷 부분은 역동적 경로 세그먼트에 대한 식별자로, params 객체의 프로퍼티 이름이 된다.

이렇게 url에 인코딩된 데이터를 잡을 수 있다.

일반적으로 품목이나 제품의 id같은 것을 url에 인코딩하여 사용한다.

4. 동적 라우트에 링크 추가하기

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

//보통 백엔드에서 데이터를 받아오겠지만 여기선 일단 더미데이터 생성
const PRODUCTS_DUMMY = [
  { id: "p1", title: "Product 1" },
  { id: "p2", title: "Product 2" },
  { id: "p3", title: "Product 3" },
];

const ProductsPage = () => {
  return (
    <>
      <h1>Products</h1>
      <ul>
        {PRODUCTS_DUMMY.map((prod) => (
          <li key={prod.id}>
            <Link to={`/products/${prod.id}`}>{prod.title}</Link>
          </li>
        ))}
      </ul>
    </>
  );
};

export default ProductsPage;

이렇게 역동적 경로 파라미터가 있는 라우트에 대해 링크를 생성하고 구축할 수 있다.


📝 상대 경로와 절대 경로

라우트는 작동해야 하는 경로이다.

1. "/"로 시작하는 절대 경로

현재 아래처럼 라우트를 설정하고 있다.

  • 루트 래퍼 라우트
    ㄴ자녀 라우트 몇 개
const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    errorElement: <ErrorPage />,
    children: [
      { path: "/", element: <HomePage /> },
      { path: "/products", element: <ProductsPage /> },
      { path: "/products/:id", element: <ProductDetail /> },
    ],
  },
]);

여기서 정의하는 모든 경로는 /로 시작하는 절대 경로(absolute paths)이다.
이 의미는 항상 도메인 이름 뒤에서 부터 나타난다는 의미로, 중요한 세부 정보이다.

예시

예를 들어 래퍼 경로를 "/root"로 변경하여 재로딩 하면 아무 페이지도 안뜬다.

const router = createBrowserRouter([
  {
    path: "/root",
    element: <RootLayout />,
    errorElement: <ErrorPage />,
    children: [
      { path: "/", element: <HomePage /> },
      { path: "/products", element: <ProductsPage /> },
      { path: "/products/:id", element: <ProductDetail /> },
    ],
  },
]);

url에 "/root", "/root/products", "/products"를 모두 입력해 봐도 모두 빈 화면만 나오는데 그 이유는 절대경로인 "/""/root"에 중첩되었기 때문이다.


  • 즉, /root로 시작하는 모든 페이지를 관리하는 부모 라우트의 자녀 페이지들이 /root로 시작하지 않기 때문에 에러가 발생한다.

2. "/"가 없으면 상대 경로

이때 자녀 라우트 앞에 있는 /를 제거하면 라우트 정의 경로가 상대 경로로 변한다.
그러면 자동으로 부모 라우트인 래퍼 라우트의 "/root" 경로 뒤에 첨부된다.
즉, 현재 활성 경로 뒤에 첨부된다.

const router = createBrowserRouter([
  {
    path: "/root",
    element: <RootLayout />,
    errorElement: <ErrorPage />,
    children: [
      { path: "", element: <HomePage /> },
      { path: "products", element: <ProductsPage /> },
      { path: "products/:id", element: <ProductDetail /> },
    ],
  },
]);

이렇게 상대 경로로 된 자녀 라우트가 있다면 리액트 라우터는 부모 라우트의 경로를 살펴보고 자녀 라우트를 부모 라우트 경로의 뒤에 첨부하므로 에러가 발생하지 않는다.

따라서 나머지 페이지와 네비게이션에서도 /를 삭제하여 모두 절대경로가 되게 하면 잘 작동한다.

  • 특히 상품 상세 페이지로 가는 링크를 상대경로로 바꾸려면, 앞에 /products/는 빼고 제품의 아이디만 적어주면 된다. 왜냐하면 상대경로를 상요하면 현재 활성 경로 뒤에 첨부되기 때문이다.
<ul>
  {PRODUCTS_DUMMY.map((prod) => (
    <li key={prod.id}>
      <Link to={prod.id}>{prod.title}</Link>
    </li>
  ))}
</ul>
  • <Link to={product/${prod.id}}> 라고 적을 경우 url에 /product/product/id 이렇게 프로덕트가 두 번 표시되어 버리니 주의!

3. Link의 특수한 relative 프로퍼티

제품 상세 페이지에서 뒤로가기 링크를 추가하여 뒤로가기를 클릭했을 때 상품 리스트 페이지로 돌아가도록 해보자.

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

const ProductDetail = () => {
  const params = useParams();
  const { id } = params;

  return (
    <>
      <h1>Product Detail</h1>
      <p>{id}</p>
      <p>
        <Link to="..">
          뒤로가기
        </Link>
      </p>
    </>
  );
};

export default ProductDetail;
  • <Link to="..">
    ..은 활성이었던 경로나 라우트로 돌아간다는 뜻이다.

그런데 뒤로가기를 클릭하니 홈으로 가버린다.
/products/p1, 두개의 세그먼트가 모두 url 경로에서 제거되어 버린다.
왜냐하면 상대 경로인 .. 가 라우트 정의에 대해 상대적으로 리졸빙되었기 때문이다.

ProductDetailPage 라우트의 정의는 / 라우트의 자녀이고, products의 형제이다.
그래서 한 수준 위로 올라가면 / 홈인것이다.
이전 라우트 경로로 돌아간다면 형제가 아닌 부모의 라우트 경로로 돌아간다.

따라서 부모 경로가 아닌 한 세그먼트만 제거하기 위해서 Link의 특수 프로퍼티인 relative 프로퍼티를 추가하여 사용하면 된다.

  • relative=route
    relative 프로퍼티의 기본값은 route로 라우트 정의에 대해 상대적이다.
    이 세그먼트를 현재 활성인 라우트 경로에 대해 상대적으로 추가/제거한다.

  • relative=path
    path로 설정하면 리액트 라우터는 현재 활성인 경로를 살펴보고, 그 경로에서 한 세그먼트만 제거한다.
    따라서 URL에서 현재 활성인 경로에 대해 한 세그먼트를 추가/제거 제어할 수 있다.

(참고) 절대 경로가 있으면 항상 그 절대 경로가 도메인 뒤에 추가되기 때문에 relative 프로퍼티는 작동하지 않는다.

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

const ProductDetail = () => {
  const params = useParams();
  const { id } = params;

  return (
    <>
      <h1>Product Detail</h1>
      <p>{id}</p>
      <p>
        <Link to=".." relative="path">
          뒤로가기
        </Link>
      </p>
    </>
  );
};

export default ProductDetail;

이렇게 하면 뒤로가기 클릭 시 현재 url에서 활성인 경로에 대해 한 세그먼트만 제거되므로 홈이 아닌 상품 리스트 페이지로 돌아갈 수 있다.


📝 인덱스 라우트 (라우트 정의 프로퍼티: index)

상대/절대 경로와 관련된, 라우트 정의에 추가할 수 있는 특수 프로퍼티가 있다.
이 프로퍼티는 일부 라우트 정의에 추가할 수 있는데, 예를 들면 HomePage에 적용할 수 있다.

const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    errorElement: <ErrorPage />,
    children: [
      { path: "", element: <HomePage /> },
      { path: "products", element: <ProductsPage /> },
      { path: "products/:id", element: <ProductDetail /> },
    ],
  },
]);

HomePage 라우트 정의에는 보다시피 경로가 없다. 대신 부모 라우트인 RootLayout에 있는 경로와 동일한 경로로 로딩된다.
현재 래핑라우트가 필요하기 때문에 같은 경로를 가지는 두개의 라우트가 있는 것이다.
이런 경우, 즉 부모와 같은 경로를 가지는 자녀 라우트에 특수 프로퍼티인 index 프로퍼티를 추가하여 설정할 수 있다.

const router = createBrowserRouter([
  {
    path: "/",
    element: <RootLayout />,
    errorElement: <ErrorPage />,
    children: [
      { index: true, element: <HomePage /> },
      { path: "products", element: <ProductsPage /> },
      { path: "products/:id", element: <ProductDetail /> },
    ],
  },
]);

그러면 HomePage 라우트는 소위 인덱스 라우트로 변한다.
부모 라우트가 활성일 경우 표시되어야 하는 기본 라우트가 이 라우트라는 의미이다.
따라서 /에 있으면 인덱스 라우트인 HomePage가 활성화 된다.

인덱스 라우트를 꼭 써야 하는건 아니지만 가끔 이런 기본 라우트가 필요하다.
부모와 자녀 라우트가 경로가 같다면, 빈 경로를 추가하는 대신 인덱스 프로퍼티를 사용하여 같은 기능을 구현할 수도 있다는 것을 알아두자.

✅ 3. 데이터를 가져오고 보내는 방법 (Data Fetching & Submission)



profile
Always have hope🍀 & constant passion🔥

0개의 댓글