8회차 - React Router v7

1. React Router란?

React Router는 페이지 이동을 처리하는 라이브러리다. SPA에서 URL에 따라 다른 화면을 보여준다.

2. 설치

npm install react-router@7

3. 기본 설정

main.jsx

import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router';
import App from './App';

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </StrictMode>
);

App.jsx

import { Routes, Route } from 'react-router';

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
    </Routes>
  );
}

function Home() {
  return <h1>홈 페이지</h1>;
}

function About() {
  return <h1>소개 페이지</h1>;
}

4. Link로 페이지 이동

<a> 태그 대신 <Link>를 사용한다. 페이지를 새로고침하지 않는다.

import { Link } from 'react-router';

function Nav() {
  return (
    <nav>
      <Link to="/">홈</Link>
      <Link to="/about">소개</Link>
      <Link to="/contact">연락처</Link>
    </nav>
  );
}

function App() {
  return (
    <div>
      <Nav />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
      </Routes>
    </div>
  );
}

5. URL 파라미터

URL에서 동적으로 값을 받아온다.

import { useParams } from 'react-router';

function App() {
  return (
    <Routes>
      <Route path="/user/:id" element={<User />} />
    </Routes>
  );
}

function User() {
  const { id } = useParams();
  return <h1>사용자 ID: {id}</h1>;
}

// /user/123 접속 → 사용자 ID: 123

여러 파라미터

function App() {
  return (
    <Routes>
      <Route path="/post/:category/:id" element={<Post />} />
    </Routes>
  );
}

function Post() {
  const { category, id } = useParams();
  return (
    <div>
      <p>카테고리: {category}</p>
      <p>글 번호: {id}</p>
    </div>
  );
}

// /post/notice/5 → 카테고리: notice, 글 번호: 5

6. 쿼리 파라미터

URL의 ?key=value 형태 데이터를 읽는다.

import { useSearchParams } from 'react-router';

function Search() {
  const [params] = useSearchParams();
  const keyword = params.get('keyword');
  const page = params.get('page');

  return (
    <div>
      <p>검색어: {keyword}</p>
      <p>페이지: {page}</p>
    </div>
  );
}

// /search?keyword=react&page=2

쿼리 파라미터 변경

function Search() {
  const [params, setParams] = useSearchParams();

  const change = () => {
    setParams({ keyword: 'vue', page: '3' });
  };

  return <button onClick={change}>변경</button>;
}

7. 프로그래밍 방식 이동

버튼 클릭이나 특정 동작 후 페이지를 이동한다.

import { useNavigate } from 'react-router';

function Login() {
  const navigate = useNavigate();

  const login = () => {
    // 로그인 처리 후
    navigate('/home');
  };

  const back = () => {
    navigate(-1); // 뒤로가기
  };

  return (
    <div>
      <button onClick={login}>로그인</button>
      <button onClick={back}>뒤로</button>
    </div>
  );
}

8. 중첩 라우트

레이아웃 안에 다른 페이지를 표시한다.

import { Outlet } from 'react-router';

function App() {
  return (
    <Routes>
      <Route path="/" element={<Layout />}>
        <Route index element={<Home />} />
        <Route path="about" element={<About />} />
        <Route path="contact" element={<Contact />} />
      </Route>
    </Routes>
  );
}

function Layout() {
  return (
    <div>
      <header>
        <nav>
          <Link to="/">홈</Link>
          <Link to="/about">소개</Link>
          <Link to="/contact">연락처</Link>
        </nav>
      </header>
      <main>
        <Outlet />
      </main>
      <footer>푸터</footer>
    </div>
  );
}

9. 404 페이지

일치하는 경로가 없을 때 표시한다.

function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
      <Route path="*" element={<NotFound />} />
    </Routes>
  );
}

function NotFound() {
  return (
    <div>
      <h1>404 - 페이지를 찾을 수 없습니다</h1>
      <Link to="/">홈으로 가기</Link>
    </div>
  );
}

10. NavLink로 활성 링크 표시

현재 페이지의 링크를 강조한다.

import { NavLink } from 'react-router';

function Nav() {
  return (
    <nav>
      <NavLink
        to="/"
        style={({ isActive }) => ({ color: isActive ? 'red' : 'black' })}
      >
        홈
      </NavLink>
      <NavLink
        to="/about"
        className={({ isActive }) => isActive ? 'active' : ''}
      >
        소개
      </NavLink>
    </nav>
  );
}

11. 라우트 분리

파일을 나누어 관리한다.

routes.jsx

import Home from './pages/Home';
import About from './pages/About';
import User from './pages/User';

export const routes = [
  { path: '/', element: <Home /> },
  { path: '/about', element: <About /> },
  { path: '/user/:id', element: <User /> }
];

App.jsx

import { Routes, Route } from 'react-router';
import { routes } from './routes';

function App() {
  return (
    <Routes>
      {routes.map((route, i) => (
        <Route key={i} path={route.path} element={route.element} />
      ))}
    </Routes>
  );
}

12. 실습: 블로그 앱

import { Routes, Route, Link, useParams } from 'react-router';

const posts = [
  { id: 1, title: '첫 글', content: '안녕하세요' },
  { id: 2, title: '두 번째 글', content: 'React 공부 중' },
  { id: 3, title: '세 번째 글', content: '재미있어요' }
];

function App() {
  return (
    <div>
      <nav>
        <Link to="/">홈</Link>
        <Link to="/posts">글 목록</Link>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/posts" element={<PostList />} />
        <Route path="/posts/:id" element={<PostDetail />} />
      </Routes>
    </div>
  );
}

function Home() {
  return <h1>블로그 홈</h1>;
}

function PostList() {
  return (
    <div>
      <h2>글 목록</h2>
      {posts.map(post => (
        <div key={post.id}>
          <Link to={`/posts/${post.id}`}>{post.title}</Link>
        </div>
      ))}
    </div>
  );
}

function PostDetail() {
  const { id } = useParams();
  const post = posts.find(p => p.id === Number(id));

  if (!post) return <div>글을 찾을 수 없습니다</div>;

  return (
    <div>
      <h2>{post.title}</h2>
      <p>{post.content}</p>
      <Link to="/posts">목록으로</Link>
    </div>
  );
}

13. 보호된 라우트

로그인한 사용자만 접근할 수 있는 페이지를 만든다.

import { Navigate } from 'react-router';

function ProtectedRoute({ children }) {
  const isLoggedIn = false; // 실제로는 상태 관리에서 가져옴

  if (!isLoggedIn) {
    return <Navigate to="/login" />;
  }

  return children;
}

function App() {
  return (
    <Routes>
      <Route path="/login" element={<Login />} />
      <Route
        path="/mypage"
        element={
          <ProtectedRoute>
            <MyPage />
          </ProtectedRoute>
        }
      />
    </Routes>
  );
}

14. 연습 과제

  1. 제품 목록과 상세페이지가 있는 쇼핑몰 만들기
  2. 탭 메뉴로 여러 페이지를 전환하는 앱 만들기
  3. 검색 기능이 있는 페이지 만들기 (쿼리 파라미터 사용)
  4. 로그인/로그아웃 기능 구현하기

정리