본문 바로가기

동적 라우팅과 파라미터

1. URL 파라미터

/:id 형식의 URL 패턴을 사용하면 URL의 일부분을 변수처럼 사용할 수 있습니다. 이러한 URL의 동적인 부분을 URL 파라미터라고 부르는데, 이를 활용하여 동적 라우팅을 구현할 수 있습니다.

동적 라우팅이란 하나의 라우트 설정으로 여러 다른 URL에 대응할 수 있게 해주는 방식입니다. 예를 들어, users/:userId 경로는 users/user1, users/user2와 같은 다양한 URL과 동적으로 매칭될 수 있습니다. 이렇게 설정된 동적 라우트는 URL의 특정 부분이 변경되어도 같은 컴포넌트를 렌더링하면서, 변경된 부분의 값을 파라미터로 받아 처리할 수 있게 합니다.

예를 들면, users/user1, users/user2는 사용자 프로필 페이지를 보여주는 동일한 컴포넌트를 렌더링하며, user1, user2와 같은 userId 부분이 변경되면 이를 파라미터로 받아 처리하여 해당하는 사용자의 프로필을 표시할 수 있도록 합니다. 이렇게 함으로써, 여러 다른 사용자 프로필 페이지를 위해 각각 새로운 컴포넌트를 만들 필요 없이, 하나의 컴포넌트로 다양한 사용자 프로필을 표시할 수 있게 됩니다.

2. 파라미터 설정

import {
  createBrowserRouter,
  RouterProvider,
  Link,
  useParams,
} from 'react-router-dom';

const router = createBrowserRouter([
  {
    path: '/',
    element: <Index />,
  },
  {
    path: '/one',
    element: <One name="react" />,
  },
  {
    path: '/two',
    element: <Two />,
  },
  {
    path: '/three',
    element: <Three />,
  },
  {
    path: '/blog/:id',
    element: <Blog />,
  },
]);

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

function Index() {
  return (
    <div>
      <nav>
        <Link to="/">home</Link>
        <Link to="/one">one</Link>
        <Link to="/two">two</Link>
        <Link to="/three">three</Link>
        <Link to="/blog/1">blog_1</Link>
        <Link to="/blog/2">blog_2</Link>
        <Link to="/blog/3">blog_3</Link>
      </nav>
      <h1>hello world</h1>
    </div>
  );
}

function One({ name }) {
  return <h1>{name} world</h1>;
}

function Two() {
  return <h1>hello world2</h1>;
}

function Three() {
  return <h1>hello world3</h1>;
}

function Blog() {
  const params = useParams();
  console.log(params);
  return <h1>hello Blog {params.id}</h1>;
}

export default App;

"blog_1" 링크를 클릭하면 브라우저의 URL이 /blog/1로 변경됩니다. React Router는 이 URL을 확인하고 라우트들 중에서 매칭되는 패턴을 찾습니다. 여기서는 /blog/:id 패턴의 라우트가 매칭되는데, :id 부분이 동적 파라미터로 작동하여 실제 URL의 '1'이라는 값을 받아들입니다.

매칭된 라우트는 자신의 element 속성에 정의된 Blog 컴포넌트를 렌더링합니다. 이때 Blog 컴포넌트에서 사용된 useParams 훅은 URL 파라미터를 담고 있는 객체를 반환합니다.

마찬가지로 "blog_2"나 "blog_3"을 클릭하면 같은 과정이 반복되지만, URL은 각각 /blog/2, /blog/3으로 변경되고, params 객체의 id 값도 그에 맞게 변경됩니다. 즉, 하나의 Blog 컴포넌트가 URL의 id 값에 따라 다른 내용을 보여줄 수 있게 되는 것입니다.

2.1 useParams 훅

useParams 훅은 URL의 동적 파라미터 값을 가져올 수 있는 React Router의 훅입니다. useParams를 사용하면 라우트 경로에 설정한 동적 파라미터를 컴포넌트에서 쉽게 추출하고 활용할 수 있게 해줍니다.

// 라우터 설정
{
  path: '/blog/:id',
  element: <BlogPost />,
}
import { useParams } from 'react-router-dom';

function BlogPost() {
  const params = useParams();
  console.log(params); // { id: "1" }

  return <div>블로그 포스트 {params.id}</div>;
}

이 코드를 실행하고 콘솔창을 확인하면, /blog/:id 경로에서 id에 해당하는 파라미터 값이 출력되는 것을 볼 수 있습니다. 예를 들어, "/blog/1"로 이동하면 콘솔에 {id: "1"}이 출력됩니다.


여러 파라미터를 사용하는 방법도 있습니다.

import {
  createBrowserRouter,
  RouterProvider,
  useParams,
} from 'react-router-dom';

const router = createBrowserRouter([
  {
    path: '/users/:userId',
    element: <UserProfile />,
  },
  {
    path: '/products/:category/:productId',
    element: <Product />,
  },
]);

// 단일 파라미터 사용
function UserProfile() {
  const { userId } = useParams();
  return <h1>사용자 {userId}의 프로필</h1>;
}

// 여러 파라미터 사용
function Product() {
  const { category, productId } = useParams();
  return (
    <div>
      <h1>{category} 카테고리</h1>
      <h2>상품 번호: {productId}</h2>
    </div>
  );
}

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

export default App;

2.2 useLocation 훅

useLocation은 현재 URL의 정보를 담고 있는 location 객체를 반환하는 React Router의 훅입니다. 이 location 객체는 Link 컴포넌트의 to 속성으로 전달한 정보들을 포함하며, 현재 페이지의 경로(pathname), 쿼리스트링(search), 해시(hash), 상태 정보(state) 등 다양한 정보에 접근할 수 있습니다.

import { useLocation } from 'react-router-dom';

function BlogPost() {
  const location = useLocation();
  console.log(location);

  return <div>블로그 포스트</div>;
}

현재 URL이 http://localhost:5173/blog/1?category=react#comments일 때, location 객체는 다음과 같은 정보를 포함합니다.

{
  pathname: "/blog/1",
  search: "?category=react",
  hash: "#comments",
  state: null,
  key: "default"
}

useLocation은 URL이 변경될 때마다 새로운 location 객체를 반환합니다. 이를 활용하여 URL 변경을 감지하고 특정 작업을 수행할 수 있습니다.

useParams vs useLocation

  • useParams: URL 파라미터 값만 필요할 때 사용합니다. (예: /blog/:id에서 id 값)
  • useLocation: 전체 URL 정보가 필요할 때 사용합니다. (pathname, search, hash, state 등)

일반적으로 URL 파라미터만 필요한 경우 useParams를 사용하는 것이 더 간결합니다.

3. useSearchParams

useSearchParams 훅은 URL의 쿼리스트링을 다루기 위한 훅입니다. 쿼리스트링을 읽고 수정할 수 있으며, URLSearchParams 인터페이스와 유사하게 동작합니다.

URLSearchParams란?

URL의 쿼리스트링을 쉽게 다룰 수 있게 해주는 JavaScript 내장 Web API입니다. 문자열을 직접 파싱하지 않고도 쿼리 파라미터를 읽고, 추가하고, 수정하고, 삭제할 수 있는 메서드를 제공합니다.

// 쿼리스트링으로 URLSearchParams 생성
const params = new URLSearchParams('?category=electronics&sort=price');

params.get('category'); // "electronics"
params.get('sort'); // "price"
params.has('category'); // true
params.toString(); // "category=electronics&sort=price"

useSearchParams는 배열을 반환하며, 첫 번째 요소는 현재 쿼리스트링을 담은 URLSearchParams 객체이고, 두 번째 요소는 쿼리스트링을 변경하는 함수입니다. useState와 비슷한 형태로 동작합니다.

import { useSearchParams } from 'react-router-dom';

function ProductList() {
  const [searchParams, setSearchParams] = useSearchParams();

  // 쿼리스트링 값 읽기
  const category = searchParams.get('category');
  const sort = searchParams.get('sort');
  const page = searchParams.get('page') || '1';

  // 쿼리스트링 변경하기
  const handleCategoryChange = (newCategory) => {
    setSearchParams({ category: newCategory, sort, page: '1' });
  };

  const handleSortChange = (newSort) => {
    setSearchParams({ category, sort: newSort, page });
  };

  const handlePageChange = (newPage) => {
    setSearchParams({ category, sort, page: newPage });
  };

  return (
    <div>
      <h1>상품 목록</h1>
      <p>카테고리: {category || '전체'}</p>
      <p>정렬: {sort || '기본'}</p>
      <p>페이지: {page}</p>

      <div>
        <button onClick={() => handleCategoryChange('electronics')}>
          전자제품
        </button>
        <button onClick={() => handleCategoryChange('clothing')}>의류</button>
      </div>

      <div>
        <button onClick={() => handleSortChange('price')}>가격순</button>
        <button onClick={() => handleSortChange('name')}>이름순</button>
      </div>

      <div>
        <button onClick={() => handlePageChange(String(Number(page) - 1))}>
          이전
        </button>
        <button onClick={() => handlePageChange(String(Number(page) + 1))}>
          다음
        </button>
      </div>
    </div>
  );
}

3.1 searchParams 메서드

searchParams 객체는 다양한 메서드를 제공합니다.

const [searchParams, setSearchParams] = useSearchParams();

// 값 읽기
searchParams.get('key'); // 단일 값 가져오기 (없으면 null)
searchParams.getAll('key'); // 같은 키의 모든 값 배열로 가져오기
searchParams.has('key'); // 키 존재 여부 확인
searchParams.toString(); // 전체 쿼리스트링 문자열로 반환

// 값 순회
searchParams.entries(); // [key, value] 쌍의 이터레이터
searchParams.keys(); // 모든 키의 이터레이터
searchParams.values(); // 모든 값의 이터레이터

// 전체 쿼리스트링 순회
for (const [key, value] of searchParams) {
  console.log(key, value);
}

3.2 setSearchParams 사용법

setSearchParams 함수로 쿼리스트링을 변경할 수 있습니다.

// 객체로 전달 (기존 쿼리스트링을 완전히 대체)
setSearchParams({ category: 'electronics', page: '1' });

// 함수로 전달 (기존 값을 기반으로 수정)
setSearchParams((prev) => {
  prev.set('page', '2');
  return prev;
});

// 특정 파라미터만 삭제
setSearchParams((prev) => {
  prev.delete('sort');
  return prev;
});

// replace 옵션 (히스토리에 새 항목 추가하지 않음)
setSearchParams({ page: '2' }, { replace: true });

URL 파라미터 vs 쿼리스트링

  • URL 파라미터 (/products/:id): 리소스를 식별하는 필수 정보에 사용합니다. (예: 상품 ID, 사용자 ID)
  • 쿼리스트링 (?sort=price&page=1): 선택적인 필터링, 정렬, 페이지네이션 등에 사용합니다.
// URL 파라미터 예시
/products/123          // 123번 상품 상세 페이지

// 쿼리스트링 예시
/products?category=electronics&sort=price&page=2

4. state를 통한 데이터 전달

Link나 navigate를 통해 페이지 이동 시 state를 함께 전달할 수 있습니다. state는 URL에 표시되지 않으며, useLocation 훅을 통해 접근할 수 있습니다.

// 데이터를 전달하는 페이지
import { Link, useNavigate } from 'react-router-dom';

function ProductList() {
  const navigate = useNavigate();
  const product = { id: 1, name: '맥북 프로', price: 3360000 };

  return (
    <div>
      {/* Link로 state 전달 */}
      <Link to={`/product/${product.id}`} state={{ product }}>
        {product.name}
      </Link>

      {/* navigate로 state 전달 */}
      <button
        onClick={() =>
          navigate(`/product/${product.id}`, { state: { product } })
        }
      >
        상세 보기
      </button>
    </div>
  );
}
// 데이터를 받는 페이지
import { useLocation, useParams } from 'react-router-dom';

function ProductDetail() {
  const { id } = useParams();
  const location = useLocation();
  const { product } = location.state || {};

  // state가 없는 경우 (직접 URL 접근 등)
  if (!product) {
    return <div>상품 ID {id}의 정보를 불러오는 중...</div>;
  }

  return (
    <div>
      <h1>{product.name}</h1>
      <p>가격: {product.price.toLocaleString()}원</p>
    </div>
  );
}

state 사용 시 주의사항

state는 페이지 이동을 통해서만 전달되므로, 사용자가 URL을 직접 입력하거나 새로고침하면 state가 없어집니다. 따라서 state에만 의존하지 말고, 필요한 데이터는 URL 파라미터나 API 호출을 통해 가져올 수 있도록 해야 합니다.

state는 목록에서 상세 페이지로 이동할 때 이미 불러온 데이터를 함께 전달하여 불필요한 API 호출을 줄이거나, 이전 페이지 정보(어디서 왔는지)를 전달하여 "뒤로 가기" 버튼의 동작을 커스터마이징할 때 유용합니다.