라우팅
라우팅(Routing)이란 웹 애플리케이션에서 사용자가 URL을 통해 페이지에 접근할 때 해당 URL에 따라 서로 다른 컴포넌트를 보여주는 것을 의미한다.
일반적으로, 브라우저에서 URL을 입력하거나 링크를 클릭하면 URL의 경로(path)와 쿼리(query) 부분을 분석하여 해당하는 컴포넌트를 렌더링한다. 이 과정을 라우팅이라고 한다.
라우팅은 SPA(Single Page Application)를 구현하는 데 필수적이며, SPA는 초기 로딩 시 서버에서 모든 컨텐츠를 불러오는 것이 아니라 필요한 데이터만 가져와 화면을 구성하기 때문에, URL에 따라 다른 화면을 보여주는 라우팅이 필요하다. 또한, 브라우저의 뒤로 가기/앞으로 가기 버튼과 같은 브라우저 기능과의 호환성을 유지하기 위해서도 라우팅이 필요하다.
React에서는 대표적으로 react-router 라이브러리를 사용하여 라우팅을 구현한다. react-router를 사용하면 URL에 따라 렌더링될 컴포넌트를 선택하고, URL을 변경하여 다른 컴포넌트를 렌더링하는 등의 기능을 구현할 수 있다.
Home v6.9.0
I'm on v5 The migration guide will help you migrate incrementally and keep shipping along the way. Or, do it all in one yolo commit! Either way, we've got you covered to start using the new features right away.
reactrouter.com
또한, Next.js라는 프레임워크가 있다. 리액트 프로젝트 설정을 하는 기능, 라우팅 시스템, 최적화, 다국어 시스템 지원, 서버 사이드 렌더링 등 다양한 기능을 제공한다. 이 프레임워크의 라우팅 시스템은 파일 경로 기반으로 작동한다. 리액트 라우터의 대안으로 많이 사용된다.
Next.js by Vercel - The React Framework
Production grade React applications that scale. The world’s leading companies use Next.js by Vercel to build static and dynamic websites and web applications.
nextjs.org
SPA
SPA (Single Page Application)는 하나의 웹 페이지로 구성된 애플리케이션이다. 전통적인 웹 페이지는 매번 새로운 페이지를 로딩하여 보여주기 때문에, 페이지 이동이 일어날 때마다 서버로부터 새로운 HTML, CSS 및 JavaScript 파일을 다운로드해야 한다. 하지만 SPA는 처음 한 번만 페이지를 로드하고, 그 이후에는 필요한 데이터만 서버로부터 받아와 동적으로 페이지를 변경한다.
SPA는 일반적으로 AJAX 기술을 사용하여 데이터를 비동기적으로 가져오고, JavaScript를 사용하여 동적으로 페이지를 렌더링한다. 이러한 방식은 사용자가 애플리케이션에서 상호작용할 때마다 새로운 페이지를 로드하지 않고도 빠르게 데이터를 가져오고 렌더링할 수 있다.
SPA는 더 나은 사용자 경험과 더 빠른 응답 시간을 제공할 수 있다. 또한, SPA는 모바일 기기와 같은 저사양 기기에서도 빠르게 작동할 수 있기 때문에 모바일 최적화 애플리케이션에 매우 적합하다. 그러나, 검색 엔진 최적화(SEO)에 대한 문제와 초기 페이지 로딩 속도에 대한 고려가 필요하다.
리액트 라우터 적용 및 기본 사용법
프로젝트 생성
npx create-react-app router-tutorial --template typescript
cd router-tutorial
npm install react-router-dom @types/react-router-dom
프로젝트 라우터 적용
프로젝트에 리액트 라우터를 작용할 때는 src/index.tsx 파일에서 BrowserRouter라는 컴포넌트를 사용해주면 된다. 이 컴포넌트는 웹 애플리케이션에 HTML5의 History API를 사용해 페이지를 새로 불러오지 않고도 주소를 변경하고 현재 주소의 경로에 관련된 정보를 리액트 컴포넌트에서 사용할 수 있도록 해 준다.
// index.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
reportWebVitals();
페이지 컴포넌트 생성
리액트 라우터를 통해 여러 페이지로 구성된 웹 애플리케이션을 만들기 위해 각 페이지에서 사용할 컴포넌트를 만들 차례이다. 사용자가 웹 사이트에 들어왔을 때 가장 먼저 보여지게 될 Home 페이지 컴포넌트와 웹 사이트를 소개하는 About 페이지 컴포넌트를 만들어보자.
// src/pages/Home.tsx
export default function Home() {
return (
<div>
<h1>Home</h1>
<p>가장 먼저 보여지는 페이지입니다.</p>
</div>
);
}
// src/pages/About.tsx
export default function About() {
return (
<div>
<h1>소개</h1>
<p>리액트 라우터를 사용해 보는 프로젝트입니다.</p>
</div>
);
}
이 컴포넌트들은 꼭 pages 경로에 넣을 필요는 없다. 다른 이름의 디렉토리도 괜찮고, src 디렉토리에 바로 넣어도 된다. 그저 페이지 컴포넌트를 다른 파일들과 구분하기 위함이다.
Route 컴포넌트로 특정 경로에 원하는 컴포넌트 보여주기
사용자의 브라우저 주소 경로에 따라 우리가 원하는 컴포넌트를 보여주려면 Route라는 컴포넌트를 통해 라우트 설정을 해줘야한다.
Route 컴포넌트는 Routes 컴포넌트 내부에서 사용되어야 한다. App 컴포넌트에서 Route컴포넌트를 사용해 라우트를 설정해보자.
import { Route, Routes } from "react-router-dom";
import "./App.css";
import About from "./pages/About";
import Home from "./pages/Home";
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
);
}
export default App;
App 컴포넌트 작성 후 렌더링해보면, 다음과 같은 결과가 나올 것이다.

Link 컴포넌트를 사용해 다른 페이지로 이동하는 링크 보여주기
Link 컴포넌트는 React Router에서 제공하는 라우팅을 위한 컴포넌트 중 하나이다. 컴포넌트는 웹 어플리케이션에서 다른 페이지로 이동할 때 페이지를 새로고침 하지 않고 애플리케이션 상태를 유지하면서 페이지를 이동할 수 있도록 해준다.
Link 컴포넌트는 a 태그 대신 사용된다. a 태그는 페이지를 새로고침하기 때문에 애플리케이션의 상태를 잃어버리게 된다. 하지만 Link 컴포넌트의 경우 a 태그와 같은 클릭 이벤트를 가지고 있지만 페이지를 새로고침하지 않으며 브라우저의 주소창의 URL을 변경시켜준다.
Home 페이지에서 About 페이지로 이동할 수 있도록 Link 컴포넌트를 Home 페이지 컴포넌트에서 사용해보자. Link 컴포넌트에서는 to 속성을 이용해 이동하고자하는 페이지의 경로를 지정할 수 있다. 이 속성은 문자열 형태로 경로를 전달할 수 있다.
import { Link } from "react-router-dom";
export default function Home() {
return (
<div>
<h1>Home</h1>
<p>가장 먼저 보여지는 페이지입니다.</p>
<Link to="/about">About</Link>
</div>
);
}
이전 Home에서 Link 컴포넌트를 추가해주었다.


렌더링해보면, Home에 About 링크가 생겼고, 링크를 클릭했을 때 About 페이지가 나타나게 된다.
URL 파라미터와 쿼리스트링
페이지 주소를 정의할 때 유동적인 값을 사용해야할 때도 있다. 이 경우, URL 파라미터와 쿼리스트링을 사용하느넫, 이것들은 웹 브라우저에서 특정 페이지로 이동하거나 데이터를 전송할 때 사용하는 방법이다.
URL 파라미터는 URL 경로 뒤에 슬래시(/)로 구분된 값들이다. 예를 들어, https://example.com/products/123
와 같은 URL에서 "123"은 products 페이지에서 사용될 수 있는 파라미터이다. 이 파라미터는 브라우저의 주소창에서 직접 수정하여 값을 변경할 수 있다.
쿼리스트링은 URL 경로와 물음표(?)로 구분된 값들이다. 예를 들어, https://example.com/search?q=apple&category=fruits
와 같은 URL에서 q=apple 과 category=fruits 는 검색 결과를 필터링하는 데 사용될 수 있는 쿼리스트링 매개변수이다. 이 매개변수들은 브라우저에서 자동으로 생성되는 것이 아니라, JavaScript 코드에서 동적으로 생성하고 사용할 수 있다. 쿼리스트링은 주소창에서 직접 수정할 수 있으므로 보안에 민감한 데이터는 쿼리스트링으로 전송하지 않는 것이 좋다.
리액트 라우터에서는 URL 파라미터와 쿼리스트링을 다루기 위해 useParams와 useLocation 훅을 제공한다. useParams는 URL 파라미터를 추출하는 데 사용되며, useLocation은 현재 URL 정보를 담고 있는 객체를 반환하며, 이 객체의 search 프로퍼티를 통해 쿼리스트링 값을 추출할 수 있다.
URL 파라미터
우선 URL 파라미터를 사용하는 방법에 대해 알아보자. 이를 사용하기 위해 새로운 페이지 컴포넌트를 만들어보자. pages 경로에 다음과 같이 Profile 컴포넌트를 작성한다.
// src/pages/Profile.tsx
import { useParams } from "react-router-dom";
interface ProfileData {
[username: string]: {
name: string;
message: string;
};
}
const data: ProfileData = {
chanwoong1: {
name: "찬웅",
message: "hello",
},
abcd: {
name: "alphabet",
message: "efgh",
},
};
function Profile() {
const { username } = useParams<{ username: string }>();
let profile = null;
if (typeof username !== "undefined") {
profile = data[username];
}
return (
<div>
<h1>User Profile</h1>
{profile ? (
<div>
<h2>{profile.name}</h2>
<p>{profile.message}</p>
</div>
) : (
<p>존재하지 않는 프로필 입니다.</p>
)}
</div>
);
}
export default Profile;
// src/App.tsx
import { Route, Routes } from "react-router-dom";
import "./App.css";
import About from "./pages/About";
import Home from "./pages/Home";
import Profile from "./pages/Profile";
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
</Routes>
);
}
export default App;
// src/pages/Home.tsx
import { Link } from "react-router-dom";
export default function Home() {
return (
<div>
<h1>Home</h1>
<p>가장 먼저 보여지는 페이지입니다.</p>
<ul>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/profiles/chanwoong1">profile1</Link>
</li>
<li>
<Link to="/profiles/abcd">profile2</Link>
</li>
<li>
<Link to="/profiles/void">존재하지 않는 프로필</Link>
</li>
</ul>
</div>
);
}
렌더링 해보면 각각의 결과가 다 잘 나오는 것을 확인할 수 있다.




쿼리스트링
쿼리스트링은 URL의 끝 "?" 뒤에 파라미터를 나타내느 문자열을 추가해 서버에 요청할 때 추가 정보를 전달하는 방법이다. 예를 들어, 다음과 같은 URL이 있을 경우
https://example.com/search?q=typescript&page=1
여기서 ?q=typescript&page=1
은 쿼리스트링으로, q와 page는 파라미터 이름이며, typescript와 1은 각각 파리미터 값이다.
우선, 쿼리스트링을 화면에 띄워보는 작업을 수행해보자. 다음은 About 페이지 컴포넌트의 쿼리스트링을 띄우는 방법이다.
import { useLocation } from "react-router-dom";
export default function About() {
const location = useLocation();
return (
<div>
<h1>소개</h1>
<p>리액트 라우터를 사용해 보는 프로젝트입니다.</p>
<p>쿼리스트링 : {location.search}</p>
</div>
);
}
이와 같이 수정 후, About 페이지로 들어가보면, 다음과 같은 화면이 나온다.

현재는 쿼리스트링으로 아무런 값이 나타나지 않지만, 주소 뒤에 ?detail=true&page=2
라고 직접 입력해보자.

이런 식으로 값이 표시되고 있다. 이 문자열에서 ?를 지우고 &를 분리한 뒤 key와 value로 파싱하는 작업을 거쳐야 하는데, 이 작업은 useSearchParams라는 Hook을 통해 쉽게 다룰 수 있다.
useSearchParam을 사용해보기 전에, 직접 파싱하는 방법의 예시이다.
function parseQueryString(queryString: string): Record<string, string> {
const query: Record<string, string> = {};
const pairs = queryString.slice(1).split("&");
for (const pair of pairs) {
const [key, value] = pair.split("=");
if (key && value) {
query[key] = decodeURIComponent(value);
}
}
return query;
}
const query = parseQueryString(location.search); // { q: "typescript", page: "1" }
const q = query.q; // "typescript"
const page = query.page; // "1"
slice(1)로 ?를 제거하고 split("&")으로 &문자열을 분리해주었다. 그리고나서, 분리된 key와 value를 각 변수에 저장해주는 방식이다.
이제 useSearchParams를 이용해 About 컴포넌트를 파싱해보자.
import { useSearchParams } from "react-router-dom";
export default function About() {
const [searchParams, setSearchParams] = useSearchParams();
const detail = searchParams.get("detail");
const page = parseInt(searchParams.get("page") || "0");
function onToggleDetail() {
setSearchParams((prev) => ({
...prev,
detail: detail === "true" ? "false" : "true",
page,
}));
}
function onIncreasePage() {
setSearchParams((prev) => ({ ...prev, detail, page: page + 1 }));
}
return (
<div>
<h1>소개</h1>
<p>리액트 라우터를 사용해 보는 프로젝트입니다.</p>
<p>detail : {detail}</p>
<p>page : {page}</p>
<button onClick={onToggleDetail}>Toggle detail</button>
<button onClick={onIncreasePage}>page + 1</button>
</div>
);
}
useStateParams는 배열 타입의 값을 반환하며, 첫 번째 원소는 쿼리파라미터를 조회하거나 수정하는 메서드들이 담긴 객체를 반환한다. get 메서드를 통해 특정 쿼리파라미터를 조회할 수 있고, set 메서드를 통해 특정 쿼리파라미터를 업데이트할 수 있다. 만약 조회 시 쿼리파라미터가 존재하지 않는다면 null로 조회된다. 두 번째 원소는 쿼리파라미터를 객체 형태로 업데이트 할 수 있는 함수를 반환한다.
쿼리파라미터를 사용할 때 주의할 점은 쿼리파라미터를 조회할 때의 값은 무조건 string이라는 것이다. 즉, true / false 값을 넣는다면 값을 비교할 때 'true'와 같이 따옴표로 감싸서 비교해야하고, 숫자를 다룰 경우 parseInt를 사용해줘야한다.


다시 렌더링했을 때, 버튼 이벤트에 따라 값이 바뀌는 것을 확인할 수 있고, URL도 변하는것을 확인할 수 있다.
중첩 라우트
리액트에서 중첩된 라우트(Nested Route)란 라우트 안에 또 다른 라우트를 중첩시켜 구성하는 것을 의미한다. 중첩된 라우트는 복잡한 UI를 구성하는 데 매우 유용하며, 대규모 애플리케이션에서도 유지보수성을 높이는 데 도움이 된다.
중첩된 라우트를 구현하려면, Route 컴포넌트를 중첩해서 사용하면 된다. 예를 들어, 다음과 같이 중첩된 라우트를 구성할 수 있다.
// src/pages/Articles.tsx
import { Link } from "react-router-dom";
export default function Articles() {
return (
<ul>
<li>
<Link to="/articles/1">게시글 1</Link>
</li>
<li>
<Link to="/articles/2">게시글 2</Link>
</li>
<li>
<Link to="/articles/3">게시글 3</Link>
</li>
</ul>
);
}
// src/pages/Article.tsx
import { useParams } from "react-router-dom";
export default function Article() {
const { id } = useParams();
return (
<div>
<h2>게시글 {id}</h2>
</div>
);
}
그리고 App 컴포넌트에서 이 두 컴포넌트의 라우트를 설정해보자.
import { Route, Routes } from "react-router-dom";
import "./App.css";
import About from "./pages/About";
import Article from "./pages/Article";
import Articles from "./pages/Articles";
import Home from "./pages/Home";
import Profile from "./pages/Profile";
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
<Route path="/articles" element={<Articles />} />
<Route path="/articles/:id" element={<Article />} />
</Routes>
);
}
export default App;
그리고나서 Home 컴포넌트에서 게시글 목록 페이지로 가는 링크를 추가하자.
import { Link } from "react-router-dom";
export default function Home() {
return (
<div>
<h1>Home</h1>
<p>가장 먼저 보여지는 페이지입니다.</p>
<ul>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/profiles/chanwoong1">profile1</Link>
</li>
<li>
<Link to="/profiles/abcd">profile2</Link>
</li>
<li>
<Link to="/profiles/void">존재하지 않는 프로필</Link>
</li>
<li>
<Link to="/articles">게시글 목록</Link>
</li>
</ul>
</div>
);
}
코드 추가 및 수정 후, 다시 렌더링해보면 결과를 확인할 수 있다.



설정해준 값대로 주소와 페이지가 잘 나타나고 있다.
중첩된 라우트를 사용한다면 좀 더 나은 방식으로 구현할 수 있다. 이번에는 중첩된 라우트 형태로 라우트를 설정해보자.
App 컴포넌트와 Article 컴포넌트를 수정하는 방식으로 게시글 하단에 게시글 목록을 나타나게 할 수 있다.
// src/App.tsx
// (...)
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
<Route path="/articles" element={<Articles />}>
<Route path=":id" element={<Article />} />
</Route>
</Routes>
);
}
export default App;
// src/pages/Articles.tsx
import { Link, Outlet } from "react-router-dom";
export default function Articles() {
return (
<div>
<Outlet />
<ul>
<li>
<Link to="/articles/1">게시글 1</Link>
</li>
<li>
<Link to="/articles/2">게시글 2</Link>
</li>
<li>
<Link to="/articles/3">게시글 3</Link>
</li>
</ul>
</div>
);
}
여기서 사용된 Outlet은 리액트에서 사용되는 컴포넌트로, 중첩된 라우트를 렌더링할 때 사용된다. Outlet은 라우터가 경로에 따라 렌더링할 수 있는 컴포넌트를 찾아서 그 결과를 렌더링해준다.

다시 렌더링해보면 게시글 하단에 게시글 목록이 잘 나타나고 있음을 확인할 수 있다.
공통 레이아웃 컴포넌트
중첩된 라우트와 Outlet은 페이지끼리 공통적으로 보여줘야하는 레이아웃이 있을 경우에도 유용하게 사용할 수 있다. 이 컴포넌트는 보통 애플리케이션의 헤더, 푸터 등과 같이 여러 페이지에서 반복되는 요소를 포함하게 된다.
이것을 구현해보기 위한 Layout 컴포넌트를 만들어보자.
// src/Layout.tsx
import { Outlet } from "react-router-dom";
export default function Layout() {
return (
<div>
<header style={{ background: "pink", padding: 16, fontSize: 24 }}>
header
</header>
<main>
<Outlet />
</main>
</div>
);
}
// src/App.tsx
import { Route, Routes } from "react-router-dom";
import "./App.css";
import Layout from "./Layout";
import About from "./pages/About";
import Article from "./pages/Article";
import Articles from "./pages/Articles";
import Home from "./pages/Home";
import Profile from "./pages/Profile";
function App() {
return (
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
</Route>
<Route path="/articles" element={<Articles />}>
<Route path=":id" element={<Article />} />
</Route>
</Routes>
);
}
export default App;
이런식으로 수정하고 다시 렌더링해보면


이와 같이 다른 페이지들에서 공통 레이아웃을 가지고 있는 것을 볼 수 있다.
리액트 라우터 부가 기능
useNavigate
useNavigate는 Link 컴포넌트를 사용하지 않고 다른 페이지로 이동해야하는 상황에 사용하는 Hook이다. 이 함수를 사용하면 스크립트 내에서 URL을 조작할 수 있다. useNavigate는 현재 URL을 기반으로 새 URL로 이동하는 데 사용할 수 있는 navigate 함수를 반환한다. 이 함수를 사용하면 URL을 변경하거나, 새로운 URL로 이동할 수 있다.
Layout 컴포넌트를 다음과 같이 수정해보자.
import { Outlet, useNavigate } from "react-router-dom";
export default function Layout() {
const navigate = useNavigate();
function goBack() {
navigate(-1);
}
function goArticles() {
navigate("/articles", { replace: true });
}
return (
<div>
<header style={{ background: "pink", padding: 16, fontSize: 24 }}>
<button onClick={goBack}>뒤로가기</button>
<button onClick={goArticles}>게시글 목록</button>
</header>
<main>
<Outlet />
</main>
</div>
);
}

이런식으로 버튼이 생기고, 클릭했을 때, 뒤로 가거나 게시글 목록으로 이동하는 것을 확인할 수 있다.
여기서, goBack 함수에서 navigate 함수의 인자로 -1을 전달하여 브라우저의 뒤로가기 기능을 수행하도록 했다. 또, goArticle 함수의 replace: true 설정은 해당 경로로 이동하며, 이동한 경로를 이전 기록에서 삭제한다는 의미이다.
NavLink
NavLink 컴포넌트는 Link와 같이 클릭하면 경로를 변경할 수 있는 요소이다. NavLink는 Link와 달리 현재 경로와 일치하는 경우 자동으로 스타일을 적용할 수 있다.
Articles 컴포넌트에서 NavLink를 사용해보자.
import { NavLink, Outlet } from "react-router-dom";
export default function Articles() {
const activeStyle = {
color: "green",
fontSize: 21,
};
return (
<div>
<Outlet />
<ul>
<li>
<NavLink
to="/articles/1"
style={({ isActive }) => (isActive ? activeStyle : undefined)}
>
게시글 1
</NavLink>
</li>
<li>
<NavLink
to="/articles/2"
style={({ isActive }) => (isActive ? activeStyle : undefined)}
>
게시글 2
</NavLink>
</li>
<li>
<NavLink
to="/articles/3"
style={({ isActive }) => (isActive ? activeStyle : undefined)}
>
게시글 3
</NavLink>
</li>
</ul>
</div>
);
}

스타일이 변경되었음을 확인할 수 있다.
이것을 리펙토링한다면, NavLink를 감싼 또 다른 컴포넌트를 만들어서 코드의 가독성을 향상시킬 수 있다.
import { NavLink, Outlet } from "react-router-dom";
export default function Articles() {
return (
<div>
<Outlet />
<ul>
<ArticleItem id={1} />
<ArticleItem id={2} />
<ArticleItem id={3} />
</ul>
</div>
);
}
function ArticleItem({ id }: { id: number }) {
const activeStyle = {
color: "green",
fontSize: 21,
};
return (
<li>
<NavLink
to={`/articles/${id}`}
style={({ isActive }) => (isActive ? activeStyle : undefined)}
>
게시글 {id}
</NavLink>
</li>
);
}
NotFound 페이지 만들기
NotFound 페이지는 사용자가 잘못된 경로로 접근했을 때, 존재하지 않는 페이지에 접근했을 때 보여주는 페이지를 말한다.
한번 구현해보자.
// src/pages/NotFound.tsx
export default function NotFound() {
return (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 64,
position: "absolute",
width: "100%",
height: "100%",
}}
>
404
</div>
);
}
// src/App.tsx
import { Route, Routes } from "react-router-dom";
import "./App.css";
import Layout from "./Layout";
import About from "./pages/About";
import Article from "./pages/Article";
import Articles from "./pages/Articles";
import Home from "./pages/Home";
import NotFound from "./pages/NotFound";
import Profile from "./pages/Profile";
function App() {
return (
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
</Route>
<Route path="/articles" element={<Articles />}>
<Route path=":id" element={<Article />} />
</Route>
<Route path="*" element={<NotFound />} />
</Routes>
);
}
export default App;
이렇게 작성해준 뒤, 다시 렌더링해주고 localhost 주소 슬래시(/) 뒤에 아무런 문자를 입력해서 접속해보면 다음과 같은 화면이 나온다.

여기서 *는 wildcard 문자이며, 아무 텍스트나 매칭한다는 뜻이다. App 컴포넌트에 위치한 상단의 규칙들을 다 확인하고 일치하는 라우트가 없다면 이 라우트가 화면에 나타나게 된다.
Navigate 컴포넌트
Navigate 컴포넌트는 브라우저의 주소를 변경하는데 사용된다. useNavigate와 유사한 역할을 수행하지만, Navigate 컴포넌트는 일반적으로 조건부로 페이지 전환을 할 때 사용된다. 예를 들어 로그인하지 않은 사용자가 허용되지 않은 페이지에 엑세스하려 시도하는 경우, 조건부로 로그인 페이지로 전환하는 데 사용할 수 있다.
Navigate 컴포넌트는 to prop을 사용해 전환할 주소를 지정하며, 선택적으로 replace prop을 사용해 현재 페이지의 브라우저 히스토리를 대체할지 여부를 지정할 수 있다.
Navigate 컴포넌트를 이용해 로그인 페이지를 만들어보자.
// src/pages/Login.tsx
export default function Login() {
return <div>로그인 페이지</div>;
}
// src/pages/MyPage.tsx
import { Navigate } from "react-router-dom";
export default function MyPage() {
const isLogin = false;
if (!isLogin) {
return <Navigate to="/login" replace={true} />;
}
return <div>마이 페이지</div>;
}
// src/App.tsx
import { Route, Routes } from "react-router-dom";
import "./App.css";
import Layout from "./Layout";
import About from "./pages/About";
import Article from "./pages/Article";
import Articles from "./pages/Articles";
import Home from "./pages/Home";
import Login from "./pages/Login";
import MyPage from "./pages/MyPage";
import NotFound from "./pages/NotFound";
import Profile from "./pages/Profile";
function App() {
return (
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
</Route>
<Route path="/articles" element={<Articles />}>
<Route path=":id" element={<Article />} />
</Route>
<Route path="/login" element={<Login />} />
<Route path="/mypage" element={<MyPage />} />
<Route path="*" element={<NotFound />} />
</Routes>
);
}
export default App;
이렇게 입력해주고 주소 뒤에 /mypage를 입력해주면 자동으로 로그인 페이지로 넘어가게 된다.

정리
리액트 라우터를 통해 여러 주소 경로에 따라 다양한 페이지를 보여 주는 방법을 알아보았다. 점점 큰 규모의 프로젝트를 진행하다보면, 웹 브라우저에서 사용할 컴포넌트, 상태 관리 로직, 그 외 여러 기능 구현 함수들이 많아지면서 파일의 크기가 매우 커진다는 점이 있다.
현재 만든 프로젝트는 About 페이지에 접속했을 때도 지금 당장 필요하지 않은 Profile, Articles 컴포넌트의 코드까지 불러오고 있다. 이 문제를 코드 스플리팅이라는 기술을 통해 해결할 수 있다. 다음 기회에 ..
'WEB > React' 카테고리의 다른 글
리액트 Context API [react/typescript] (0) | 2023.03.21 |
---|---|
리액트 외부 API 연동 실습 [react/API/typescript] (0) | 2023.03.20 |
리액트 불변성 유지하기 [react/immer/typescript] (0) | 2023.03.18 |
리액트 컴포넌트 성능 최적화 [react/component/typescript] (0) | 2023.03.18 |
리액트 일정관리 웹 만들기 [react/typescript] (0) | 2023.03.17 |
라우팅
라우팅(Routing)이란 웹 애플리케이션에서 사용자가 URL을 통해 페이지에 접근할 때 해당 URL에 따라 서로 다른 컴포넌트를 보여주는 것을 의미한다.
일반적으로, 브라우저에서 URL을 입력하거나 링크를 클릭하면 URL의 경로(path)와 쿼리(query) 부분을 분석하여 해당하는 컴포넌트를 렌더링한다. 이 과정을 라우팅이라고 한다.
라우팅은 SPA(Single Page Application)를 구현하는 데 필수적이며, SPA는 초기 로딩 시 서버에서 모든 컨텐츠를 불러오는 것이 아니라 필요한 데이터만 가져와 화면을 구성하기 때문에, URL에 따라 다른 화면을 보여주는 라우팅이 필요하다. 또한, 브라우저의 뒤로 가기/앞으로 가기 버튼과 같은 브라우저 기능과의 호환성을 유지하기 위해서도 라우팅이 필요하다.
React에서는 대표적으로 react-router 라이브러리를 사용하여 라우팅을 구현한다. react-router를 사용하면 URL에 따라 렌더링될 컴포넌트를 선택하고, URL을 변경하여 다른 컴포넌트를 렌더링하는 등의 기능을 구현할 수 있다.
Home v6.9.0
I'm on v5 The migration guide will help you migrate incrementally and keep shipping along the way. Or, do it all in one yolo commit! Either way, we've got you covered to start using the new features right away.
reactrouter.com
또한, Next.js라는 프레임워크가 있다. 리액트 프로젝트 설정을 하는 기능, 라우팅 시스템, 최적화, 다국어 시스템 지원, 서버 사이드 렌더링 등 다양한 기능을 제공한다. 이 프레임워크의 라우팅 시스템은 파일 경로 기반으로 작동한다. 리액트 라우터의 대안으로 많이 사용된다.
Next.js by Vercel - The React Framework
Production grade React applications that scale. The world’s leading companies use Next.js by Vercel to build static and dynamic websites and web applications.
nextjs.org
SPA
SPA (Single Page Application)는 하나의 웹 페이지로 구성된 애플리케이션이다. 전통적인 웹 페이지는 매번 새로운 페이지를 로딩하여 보여주기 때문에, 페이지 이동이 일어날 때마다 서버로부터 새로운 HTML, CSS 및 JavaScript 파일을 다운로드해야 한다. 하지만 SPA는 처음 한 번만 페이지를 로드하고, 그 이후에는 필요한 데이터만 서버로부터 받아와 동적으로 페이지를 변경한다.
SPA는 일반적으로 AJAX 기술을 사용하여 데이터를 비동기적으로 가져오고, JavaScript를 사용하여 동적으로 페이지를 렌더링한다. 이러한 방식은 사용자가 애플리케이션에서 상호작용할 때마다 새로운 페이지를 로드하지 않고도 빠르게 데이터를 가져오고 렌더링할 수 있다.
SPA는 더 나은 사용자 경험과 더 빠른 응답 시간을 제공할 수 있다. 또한, SPA는 모바일 기기와 같은 저사양 기기에서도 빠르게 작동할 수 있기 때문에 모바일 최적화 애플리케이션에 매우 적합하다. 그러나, 검색 엔진 최적화(SEO)에 대한 문제와 초기 페이지 로딩 속도에 대한 고려가 필요하다.
리액트 라우터 적용 및 기본 사용법
프로젝트 생성
npx create-react-app router-tutorial --template typescript
cd router-tutorial
npm install react-router-dom @types/react-router-dom
프로젝트 라우터 적용
프로젝트에 리액트 라우터를 작용할 때는 src/index.tsx 파일에서 BrowserRouter라는 컴포넌트를 사용해주면 된다. 이 컴포넌트는 웹 애플리케이션에 HTML5의 History API를 사용해 페이지를 새로 불러오지 않고도 주소를 변경하고 현재 주소의 경로에 관련된 정보를 리액트 컴포넌트에서 사용할 수 있도록 해 준다.
// index.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
reportWebVitals();
페이지 컴포넌트 생성
리액트 라우터를 통해 여러 페이지로 구성된 웹 애플리케이션을 만들기 위해 각 페이지에서 사용할 컴포넌트를 만들 차례이다. 사용자가 웹 사이트에 들어왔을 때 가장 먼저 보여지게 될 Home 페이지 컴포넌트와 웹 사이트를 소개하는 About 페이지 컴포넌트를 만들어보자.
// src/pages/Home.tsx
export default function Home() {
return (
<div>
<h1>Home</h1>
<p>가장 먼저 보여지는 페이지입니다.</p>
</div>
);
}
// src/pages/About.tsx
export default function About() {
return (
<div>
<h1>소개</h1>
<p>리액트 라우터를 사용해 보는 프로젝트입니다.</p>
</div>
);
}
이 컴포넌트들은 꼭 pages 경로에 넣을 필요는 없다. 다른 이름의 디렉토리도 괜찮고, src 디렉토리에 바로 넣어도 된다. 그저 페이지 컴포넌트를 다른 파일들과 구분하기 위함이다.
Route 컴포넌트로 특정 경로에 원하는 컴포넌트 보여주기
사용자의 브라우저 주소 경로에 따라 우리가 원하는 컴포넌트를 보여주려면 Route라는 컴포넌트를 통해 라우트 설정을 해줘야한다.
Route 컴포넌트는 Routes 컴포넌트 내부에서 사용되어야 한다. App 컴포넌트에서 Route컴포넌트를 사용해 라우트를 설정해보자.
import { Route, Routes } from "react-router-dom";
import "./App.css";
import About from "./pages/About";
import Home from "./pages/Home";
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
);
}
export default App;
App 컴포넌트 작성 후 렌더링해보면, 다음과 같은 결과가 나올 것이다.

Link 컴포넌트를 사용해 다른 페이지로 이동하는 링크 보여주기
Link 컴포넌트는 React Router에서 제공하는 라우팅을 위한 컴포넌트 중 하나이다. 컴포넌트는 웹 어플리케이션에서 다른 페이지로 이동할 때 페이지를 새로고침 하지 않고 애플리케이션 상태를 유지하면서 페이지를 이동할 수 있도록 해준다.
Link 컴포넌트는 a 태그 대신 사용된다. a 태그는 페이지를 새로고침하기 때문에 애플리케이션의 상태를 잃어버리게 된다. 하지만 Link 컴포넌트의 경우 a 태그와 같은 클릭 이벤트를 가지고 있지만 페이지를 새로고침하지 않으며 브라우저의 주소창의 URL을 변경시켜준다.
Home 페이지에서 About 페이지로 이동할 수 있도록 Link 컴포넌트를 Home 페이지 컴포넌트에서 사용해보자. Link 컴포넌트에서는 to 속성을 이용해 이동하고자하는 페이지의 경로를 지정할 수 있다. 이 속성은 문자열 형태로 경로를 전달할 수 있다.
import { Link } from "react-router-dom";
export default function Home() {
return (
<div>
<h1>Home</h1>
<p>가장 먼저 보여지는 페이지입니다.</p>
<Link to="/about">About</Link>
</div>
);
}
이전 Home에서 Link 컴포넌트를 추가해주었다.


렌더링해보면, Home에 About 링크가 생겼고, 링크를 클릭했을 때 About 페이지가 나타나게 된다.
URL 파라미터와 쿼리스트링
페이지 주소를 정의할 때 유동적인 값을 사용해야할 때도 있다. 이 경우, URL 파라미터와 쿼리스트링을 사용하느넫, 이것들은 웹 브라우저에서 특정 페이지로 이동하거나 데이터를 전송할 때 사용하는 방법이다.
URL 파라미터는 URL 경로 뒤에 슬래시(/)로 구분된 값들이다. 예를 들어, https://example.com/products/123
와 같은 URL에서 "123"은 products 페이지에서 사용될 수 있는 파라미터이다. 이 파라미터는 브라우저의 주소창에서 직접 수정하여 값을 변경할 수 있다.
쿼리스트링은 URL 경로와 물음표(?)로 구분된 값들이다. 예를 들어, https://example.com/search?q=apple&category=fruits
와 같은 URL에서 q=apple 과 category=fruits 는 검색 결과를 필터링하는 데 사용될 수 있는 쿼리스트링 매개변수이다. 이 매개변수들은 브라우저에서 자동으로 생성되는 것이 아니라, JavaScript 코드에서 동적으로 생성하고 사용할 수 있다. 쿼리스트링은 주소창에서 직접 수정할 수 있으므로 보안에 민감한 데이터는 쿼리스트링으로 전송하지 않는 것이 좋다.
리액트 라우터에서는 URL 파라미터와 쿼리스트링을 다루기 위해 useParams와 useLocation 훅을 제공한다. useParams는 URL 파라미터를 추출하는 데 사용되며, useLocation은 현재 URL 정보를 담고 있는 객체를 반환하며, 이 객체의 search 프로퍼티를 통해 쿼리스트링 값을 추출할 수 있다.
URL 파라미터
우선 URL 파라미터를 사용하는 방법에 대해 알아보자. 이를 사용하기 위해 새로운 페이지 컴포넌트를 만들어보자. pages 경로에 다음과 같이 Profile 컴포넌트를 작성한다.
// src/pages/Profile.tsx
import { useParams } from "react-router-dom";
interface ProfileData {
[username: string]: {
name: string;
message: string;
};
}
const data: ProfileData = {
chanwoong1: {
name: "찬웅",
message: "hello",
},
abcd: {
name: "alphabet",
message: "efgh",
},
};
function Profile() {
const { username } = useParams<{ username: string }>();
let profile = null;
if (typeof username !== "undefined") {
profile = data[username];
}
return (
<div>
<h1>User Profile</h1>
{profile ? (
<div>
<h2>{profile.name}</h2>
<p>{profile.message}</p>
</div>
) : (
<p>존재하지 않는 프로필 입니다.</p>
)}
</div>
);
}
export default Profile;
// src/App.tsx
import { Route, Routes } from "react-router-dom";
import "./App.css";
import About from "./pages/About";
import Home from "./pages/Home";
import Profile from "./pages/Profile";
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
</Routes>
);
}
export default App;
// src/pages/Home.tsx
import { Link } from "react-router-dom";
export default function Home() {
return (
<div>
<h1>Home</h1>
<p>가장 먼저 보여지는 페이지입니다.</p>
<ul>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/profiles/chanwoong1">profile1</Link>
</li>
<li>
<Link to="/profiles/abcd">profile2</Link>
</li>
<li>
<Link to="/profiles/void">존재하지 않는 프로필</Link>
</li>
</ul>
</div>
);
}
렌더링 해보면 각각의 결과가 다 잘 나오는 것을 확인할 수 있다.




쿼리스트링
쿼리스트링은 URL의 끝 "?" 뒤에 파라미터를 나타내느 문자열을 추가해 서버에 요청할 때 추가 정보를 전달하는 방법이다. 예를 들어, 다음과 같은 URL이 있을 경우
https://example.com/search?q=typescript&page=1
여기서 ?q=typescript&page=1
은 쿼리스트링으로, q와 page는 파라미터 이름이며, typescript와 1은 각각 파리미터 값이다.
우선, 쿼리스트링을 화면에 띄워보는 작업을 수행해보자. 다음은 About 페이지 컴포넌트의 쿼리스트링을 띄우는 방법이다.
import { useLocation } from "react-router-dom";
export default function About() {
const location = useLocation();
return (
<div>
<h1>소개</h1>
<p>리액트 라우터를 사용해 보는 프로젝트입니다.</p>
<p>쿼리스트링 : {location.search}</p>
</div>
);
}
이와 같이 수정 후, About 페이지로 들어가보면, 다음과 같은 화면이 나온다.

현재는 쿼리스트링으로 아무런 값이 나타나지 않지만, 주소 뒤에 ?detail=true&page=2
라고 직접 입력해보자.

이런 식으로 값이 표시되고 있다. 이 문자열에서 ?를 지우고 &를 분리한 뒤 key와 value로 파싱하는 작업을 거쳐야 하는데, 이 작업은 useSearchParams라는 Hook을 통해 쉽게 다룰 수 있다.
useSearchParam을 사용해보기 전에, 직접 파싱하는 방법의 예시이다.
function parseQueryString(queryString: string): Record<string, string> {
const query: Record<string, string> = {};
const pairs = queryString.slice(1).split("&");
for (const pair of pairs) {
const [key, value] = pair.split("=");
if (key && value) {
query[key] = decodeURIComponent(value);
}
}
return query;
}
const query = parseQueryString(location.search); // { q: "typescript", page: "1" }
const q = query.q; // "typescript"
const page = query.page; // "1"
slice(1)로 ?를 제거하고 split("&")으로 &문자열을 분리해주었다. 그리고나서, 분리된 key와 value를 각 변수에 저장해주는 방식이다.
이제 useSearchParams를 이용해 About 컴포넌트를 파싱해보자.
import { useSearchParams } from "react-router-dom";
export default function About() {
const [searchParams, setSearchParams] = useSearchParams();
const detail = searchParams.get("detail");
const page = parseInt(searchParams.get("page") || "0");
function onToggleDetail() {
setSearchParams((prev) => ({
...prev,
detail: detail === "true" ? "false" : "true",
page,
}));
}
function onIncreasePage() {
setSearchParams((prev) => ({ ...prev, detail, page: page + 1 }));
}
return (
<div>
<h1>소개</h1>
<p>리액트 라우터를 사용해 보는 프로젝트입니다.</p>
<p>detail : {detail}</p>
<p>page : {page}</p>
<button onClick={onToggleDetail}>Toggle detail</button>
<button onClick={onIncreasePage}>page + 1</button>
</div>
);
}
useStateParams는 배열 타입의 값을 반환하며, 첫 번째 원소는 쿼리파라미터를 조회하거나 수정하는 메서드들이 담긴 객체를 반환한다. get 메서드를 통해 특정 쿼리파라미터를 조회할 수 있고, set 메서드를 통해 특정 쿼리파라미터를 업데이트할 수 있다. 만약 조회 시 쿼리파라미터가 존재하지 않는다면 null로 조회된다. 두 번째 원소는 쿼리파라미터를 객체 형태로 업데이트 할 수 있는 함수를 반환한다.
쿼리파라미터를 사용할 때 주의할 점은 쿼리파라미터를 조회할 때의 값은 무조건 string이라는 것이다. 즉, true / false 값을 넣는다면 값을 비교할 때 'true'와 같이 따옴표로 감싸서 비교해야하고, 숫자를 다룰 경우 parseInt를 사용해줘야한다.


다시 렌더링했을 때, 버튼 이벤트에 따라 값이 바뀌는 것을 확인할 수 있고, URL도 변하는것을 확인할 수 있다.
중첩 라우트
리액트에서 중첩된 라우트(Nested Route)란 라우트 안에 또 다른 라우트를 중첩시켜 구성하는 것을 의미한다. 중첩된 라우트는 복잡한 UI를 구성하는 데 매우 유용하며, 대규모 애플리케이션에서도 유지보수성을 높이는 데 도움이 된다.
중첩된 라우트를 구현하려면, Route 컴포넌트를 중첩해서 사용하면 된다. 예를 들어, 다음과 같이 중첩된 라우트를 구성할 수 있다.
// src/pages/Articles.tsx
import { Link } from "react-router-dom";
export default function Articles() {
return (
<ul>
<li>
<Link to="/articles/1">게시글 1</Link>
</li>
<li>
<Link to="/articles/2">게시글 2</Link>
</li>
<li>
<Link to="/articles/3">게시글 3</Link>
</li>
</ul>
);
}
// src/pages/Article.tsx
import { useParams } from "react-router-dom";
export default function Article() {
const { id } = useParams();
return (
<div>
<h2>게시글 {id}</h2>
</div>
);
}
그리고 App 컴포넌트에서 이 두 컴포넌트의 라우트를 설정해보자.
import { Route, Routes } from "react-router-dom";
import "./App.css";
import About from "./pages/About";
import Article from "./pages/Article";
import Articles from "./pages/Articles";
import Home from "./pages/Home";
import Profile from "./pages/Profile";
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
<Route path="/articles" element={<Articles />} />
<Route path="/articles/:id" element={<Article />} />
</Routes>
);
}
export default App;
그리고나서 Home 컴포넌트에서 게시글 목록 페이지로 가는 링크를 추가하자.
import { Link } from "react-router-dom";
export default function Home() {
return (
<div>
<h1>Home</h1>
<p>가장 먼저 보여지는 페이지입니다.</p>
<ul>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/profiles/chanwoong1">profile1</Link>
</li>
<li>
<Link to="/profiles/abcd">profile2</Link>
</li>
<li>
<Link to="/profiles/void">존재하지 않는 프로필</Link>
</li>
<li>
<Link to="/articles">게시글 목록</Link>
</li>
</ul>
</div>
);
}
코드 추가 및 수정 후, 다시 렌더링해보면 결과를 확인할 수 있다.



설정해준 값대로 주소와 페이지가 잘 나타나고 있다.
중첩된 라우트를 사용한다면 좀 더 나은 방식으로 구현할 수 있다. 이번에는 중첩된 라우트 형태로 라우트를 설정해보자.
App 컴포넌트와 Article 컴포넌트를 수정하는 방식으로 게시글 하단에 게시글 목록을 나타나게 할 수 있다.
// src/App.tsx
// (...)
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
<Route path="/articles" element={<Articles />}>
<Route path=":id" element={<Article />} />
</Route>
</Routes>
);
}
export default App;
// src/pages/Articles.tsx
import { Link, Outlet } from "react-router-dom";
export default function Articles() {
return (
<div>
<Outlet />
<ul>
<li>
<Link to="/articles/1">게시글 1</Link>
</li>
<li>
<Link to="/articles/2">게시글 2</Link>
</li>
<li>
<Link to="/articles/3">게시글 3</Link>
</li>
</ul>
</div>
);
}
여기서 사용된 Outlet은 리액트에서 사용되는 컴포넌트로, 중첩된 라우트를 렌더링할 때 사용된다. Outlet은 라우터가 경로에 따라 렌더링할 수 있는 컴포넌트를 찾아서 그 결과를 렌더링해준다.

다시 렌더링해보면 게시글 하단에 게시글 목록이 잘 나타나고 있음을 확인할 수 있다.
공통 레이아웃 컴포넌트
중첩된 라우트와 Outlet은 페이지끼리 공통적으로 보여줘야하는 레이아웃이 있을 경우에도 유용하게 사용할 수 있다. 이 컴포넌트는 보통 애플리케이션의 헤더, 푸터 등과 같이 여러 페이지에서 반복되는 요소를 포함하게 된다.
이것을 구현해보기 위한 Layout 컴포넌트를 만들어보자.
// src/Layout.tsx
import { Outlet } from "react-router-dom";
export default function Layout() {
return (
<div>
<header style={{ background: "pink", padding: 16, fontSize: 24 }}>
header
</header>
<main>
<Outlet />
</main>
</div>
);
}
// src/App.tsx
import { Route, Routes } from "react-router-dom";
import "./App.css";
import Layout from "./Layout";
import About from "./pages/About";
import Article from "./pages/Article";
import Articles from "./pages/Articles";
import Home from "./pages/Home";
import Profile from "./pages/Profile";
function App() {
return (
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
</Route>
<Route path="/articles" element={<Articles />}>
<Route path=":id" element={<Article />} />
</Route>
</Routes>
);
}
export default App;
이런식으로 수정하고 다시 렌더링해보면


이와 같이 다른 페이지들에서 공통 레이아웃을 가지고 있는 것을 볼 수 있다.
리액트 라우터 부가 기능
useNavigate
useNavigate는 Link 컴포넌트를 사용하지 않고 다른 페이지로 이동해야하는 상황에 사용하는 Hook이다. 이 함수를 사용하면 스크립트 내에서 URL을 조작할 수 있다. useNavigate는 현재 URL을 기반으로 새 URL로 이동하는 데 사용할 수 있는 navigate 함수를 반환한다. 이 함수를 사용하면 URL을 변경하거나, 새로운 URL로 이동할 수 있다.
Layout 컴포넌트를 다음과 같이 수정해보자.
import { Outlet, useNavigate } from "react-router-dom";
export default function Layout() {
const navigate = useNavigate();
function goBack() {
navigate(-1);
}
function goArticles() {
navigate("/articles", { replace: true });
}
return (
<div>
<header style={{ background: "pink", padding: 16, fontSize: 24 }}>
<button onClick={goBack}>뒤로가기</button>
<button onClick={goArticles}>게시글 목록</button>
</header>
<main>
<Outlet />
</main>
</div>
);
}

이런식으로 버튼이 생기고, 클릭했을 때, 뒤로 가거나 게시글 목록으로 이동하는 것을 확인할 수 있다.
여기서, goBack 함수에서 navigate 함수의 인자로 -1을 전달하여 브라우저의 뒤로가기 기능을 수행하도록 했다. 또, goArticle 함수의 replace: true 설정은 해당 경로로 이동하며, 이동한 경로를 이전 기록에서 삭제한다는 의미이다.
NavLink
NavLink 컴포넌트는 Link와 같이 클릭하면 경로를 변경할 수 있는 요소이다. NavLink는 Link와 달리 현재 경로와 일치하는 경우 자동으로 스타일을 적용할 수 있다.
Articles 컴포넌트에서 NavLink를 사용해보자.
import { NavLink, Outlet } from "react-router-dom";
export default function Articles() {
const activeStyle = {
color: "green",
fontSize: 21,
};
return (
<div>
<Outlet />
<ul>
<li>
<NavLink
to="/articles/1"
style={({ isActive }) => (isActive ? activeStyle : undefined)}
>
게시글 1
</NavLink>
</li>
<li>
<NavLink
to="/articles/2"
style={({ isActive }) => (isActive ? activeStyle : undefined)}
>
게시글 2
</NavLink>
</li>
<li>
<NavLink
to="/articles/3"
style={({ isActive }) => (isActive ? activeStyle : undefined)}
>
게시글 3
</NavLink>
</li>
</ul>
</div>
);
}

스타일이 변경되었음을 확인할 수 있다.
이것을 리펙토링한다면, NavLink를 감싼 또 다른 컴포넌트를 만들어서 코드의 가독성을 향상시킬 수 있다.
import { NavLink, Outlet } from "react-router-dom";
export default function Articles() {
return (
<div>
<Outlet />
<ul>
<ArticleItem id={1} />
<ArticleItem id={2} />
<ArticleItem id={3} />
</ul>
</div>
);
}
function ArticleItem({ id }: { id: number }) {
const activeStyle = {
color: "green",
fontSize: 21,
};
return (
<li>
<NavLink
to={`/articles/${id}`}
style={({ isActive }) => (isActive ? activeStyle : undefined)}
>
게시글 {id}
</NavLink>
</li>
);
}
NotFound 페이지 만들기
NotFound 페이지는 사용자가 잘못된 경로로 접근했을 때, 존재하지 않는 페이지에 접근했을 때 보여주는 페이지를 말한다.
한번 구현해보자.
// src/pages/NotFound.tsx
export default function NotFound() {
return (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 64,
position: "absolute",
width: "100%",
height: "100%",
}}
>
404
</div>
);
}
// src/App.tsx
import { Route, Routes } from "react-router-dom";
import "./App.css";
import Layout from "./Layout";
import About from "./pages/About";
import Article from "./pages/Article";
import Articles from "./pages/Articles";
import Home from "./pages/Home";
import NotFound from "./pages/NotFound";
import Profile from "./pages/Profile";
function App() {
return (
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
</Route>
<Route path="/articles" element={<Articles />}>
<Route path=":id" element={<Article />} />
</Route>
<Route path="*" element={<NotFound />} />
</Routes>
);
}
export default App;
이렇게 작성해준 뒤, 다시 렌더링해주고 localhost 주소 슬래시(/) 뒤에 아무런 문자를 입력해서 접속해보면 다음과 같은 화면이 나온다.

여기서 *는 wildcard 문자이며, 아무 텍스트나 매칭한다는 뜻이다. App 컴포넌트에 위치한 상단의 규칙들을 다 확인하고 일치하는 라우트가 없다면 이 라우트가 화면에 나타나게 된다.
Navigate 컴포넌트
Navigate 컴포넌트는 브라우저의 주소를 변경하는데 사용된다. useNavigate와 유사한 역할을 수행하지만, Navigate 컴포넌트는 일반적으로 조건부로 페이지 전환을 할 때 사용된다. 예를 들어 로그인하지 않은 사용자가 허용되지 않은 페이지에 엑세스하려 시도하는 경우, 조건부로 로그인 페이지로 전환하는 데 사용할 수 있다.
Navigate 컴포넌트는 to prop을 사용해 전환할 주소를 지정하며, 선택적으로 replace prop을 사용해 현재 페이지의 브라우저 히스토리를 대체할지 여부를 지정할 수 있다.
Navigate 컴포넌트를 이용해 로그인 페이지를 만들어보자.
// src/pages/Login.tsx
export default function Login() {
return <div>로그인 페이지</div>;
}
// src/pages/MyPage.tsx
import { Navigate } from "react-router-dom";
export default function MyPage() {
const isLogin = false;
if (!isLogin) {
return <Navigate to="/login" replace={true} />;
}
return <div>마이 페이지</div>;
}
// src/App.tsx
import { Route, Routes } from "react-router-dom";
import "./App.css";
import Layout from "./Layout";
import About from "./pages/About";
import Article from "./pages/Article";
import Articles from "./pages/Articles";
import Home from "./pages/Home";
import Login from "./pages/Login";
import MyPage from "./pages/MyPage";
import NotFound from "./pages/NotFound";
import Profile from "./pages/Profile";
function App() {
return (
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profiles/:username" element={<Profile />} />
</Route>
<Route path="/articles" element={<Articles />}>
<Route path=":id" element={<Article />} />
</Route>
<Route path="/login" element={<Login />} />
<Route path="/mypage" element={<MyPage />} />
<Route path="*" element={<NotFound />} />
</Routes>
);
}
export default App;
이렇게 입력해주고 주소 뒤에 /mypage를 입력해주면 자동으로 로그인 페이지로 넘어가게 된다.

정리
리액트 라우터를 통해 여러 주소 경로에 따라 다양한 페이지를 보여 주는 방법을 알아보았다. 점점 큰 규모의 프로젝트를 진행하다보면, 웹 브라우저에서 사용할 컴포넌트, 상태 관리 로직, 그 외 여러 기능 구현 함수들이 많아지면서 파일의 크기가 매우 커진다는 점이 있다.
현재 만든 프로젝트는 About 페이지에 접속했을 때도 지금 당장 필요하지 않은 Profile, Articles 컴포넌트의 코드까지 불러오고 있다. 이 문제를 코드 스플리팅이라는 기술을 통해 해결할 수 있다. 다음 기회에 ..
'WEB > React' 카테고리의 다른 글
리액트 Context API [react/typescript] (0) | 2023.03.21 |
---|---|
리액트 외부 API 연동 실습 [react/API/typescript] (0) | 2023.03.20 |
리액트 불변성 유지하기 [react/immer/typescript] (0) | 2023.03.18 |
리액트 컴포넌트 성능 최적화 [react/component/typescript] (0) | 2023.03.18 |
리액트 일정관리 웹 만들기 [react/typescript] (0) | 2023.03.17 |