
이번 글에서는 리액트 라우터를 공부하며 알게 된 지식, 간단한 예제를 통해서 리액트 라우터 기본 사용 방법에 대해서 공유드리려고 합니다.
새로운 URL 경로를 주소창에 입력 시 URL에 해당하는 새로운 페이지를 네트워크 서버상에 요청하게 되고, 서버는 새로운 HTML 파일을 받아와서 페이지 전체를 업데이트하게 됩니다.
이것은 웹 페이지의 이동 방법 중 하나이며, 웹 앱 형태 중 하나인 싱글 페이지 어플리케이션(SPA)에서 주로 사용되고 있습니다.
페이지를 이동할 때마다 서버에 웹 페이지를 요청하여 새로 갱신하는 것이 아니라 미리 해당 페이지들을 받아 놓고, 페이지 이동 시 클라이언트의 라우팅을 이용하여 화면을 갱신하는 패턴을 적용한 웹앱입니다.
과거 웹 사이트는 지금과는 달리 하나의 HTML 파일에 전달되는 정보의 양이 적었습니다. 그래서 여러 페이지로 구성되었으며, 유저가 요청할 때 마다 페이지가 새로고침되면서 서버로부터 리소스를 전달받아 해석 후 렌더링이 이루어 졌습니다. HTML 파일, 웹 어플리케이션의 뷰가 어떻게 보여질지 서버에서 담당했죠.
하지만 기술이 점차 발전됨에 따라 한 페이지에 담긴 정보의 양이 매우 커졌고, 이는 서버에서 매번 새로운 페이지를 전송해주는 것이 점차 버거워 지게 되었습니다.
그래서 문제를 해결하기 위해 등장한 기술이 SPA입니다. 브라우저는 최초 한 번에 서버에 요청하여 전체를 로드하고, 이후부터는 변경 사항이 있는 특정 부분만 데이터를 전달받아 보여주죠. 이처럼 기존에 매번 서버에서 요청하여 서버가 페이지를 재렌더링을 하던 방식과 다른게 SPA의 특징입니다.
파일을 만들고 리액트 라우터를 사용할 프로젝트 생성.
$ yarn create react-app .
그리고 해당 프로젝트 디렉토리로 이동하여 리액트 라우터를 설치.
$ yarn add react-router-dom
이렇게 하면 브라우저 라우터를 생성하고 첫 번째 경로를 구성하게 됩니다.
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import './App.css';
const router = createBrowserRouter ([ // 배열 형태
{
path: '/',
element: <p>Home</p>,
},
]);
function App() {
return <RouterProvider router={router} />;
}
export default App;

두 번째 경로 videos을 구성합니다.
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import './App.css';
const router = createBrowserRouter ([
{
path: '/',
element: <p>Home</p>,
},
{
path: '/videos',
element: <p>Videos</p>,
},
]);
function App() {
return <RouterProvider router={router} />;
}
export default App;
만약 틀린 경로를 입력하면 Not Fount 페이지가 뜨는데요, 이런 경우에는 errorElement prop을 사용하면 내가 원하는 페이지로 만들 수 있습니다.

{
path: '/',
element: <p>Home</p>,
errorElement: <p>Not Found....</p>,
},

src 디렉토리에서 components와 pages 경로를 생성하세요.
components
pages

(앱 전체 레이아웃) Root layout components 생성하세요.
// src/pages/Root/Root.jsx
import React from 'react';
import { Outlet } from 'react-router-dom';
import Navbar from '../../components/Navbar/Navbar';
import './Root.css';
export default function Root() {
return (
<div className='root'>
<Navbar />
<Outlet />
</div>
);
}
Root 경로로 설정 element
// src/App.js
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import NotFound from './pages/NotFound';
import Root from './pages/Root/Root';
const router = createBrowserRouter ([
{
path: '/',
element: <Root />,
errorElement: <NotFound />,
},
]);
function App() {
return <RouterProvider router={router} />;
}
export default App;

// src/App.js
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import './App.css';
import NotFound from './pages/NotFound';
import Videos from './pages/Videos/Videos';
import Root from './pages/Root/Root';
import Weather from './pages/Weather/Weather';
import Home from './pages/Home/Home';
const router = createBrowserRouter ([
{
path: '/',
element: <Root />,
errorElement: <NotFound />,
children: [
{ index: true, element: <Home/>},
{ path: '/videos', element: <Videos />},
{ path: '/weather', element: <Weather />},
],
},
]);
function App() {
return <RouterProvider router={router} />;
}
export default App;
link 컴포넌트는 다른 주소로 이동시키는 컴포넌트입니다. 리액트 라우터를 사용할땐 <a href=''></a> 대신 <link to='/videos'></link>를 사용합니다.
a 태그의 기본적인 속성은 페이지를 이동시키면서, 전체 페이지를 새로 불러오게 됩니다. 그렇기 때문에 link 컴포넌트를 사용하는데요, HTML5 History API를 사용하여 브라우저의 주소만 바꿀 뿐, 페이지를 새로 불러오지는 않습니다.
// src/components/Navbar.jsx
import React from 'react';
import { Link } from 'react-router-dom';
import './Navbar.css';
export default function Navbar() {
return (
<nav className='nav'>
<Link to='/'>Home</Link>
<Link to='/videos'>Videos</Link>
<Link to='/weather'>Weather</Link>
</nav>
);
}
다음은 URL 파라미터를 이용해서 상세페이지를 만드는 방법을 정리하겠습니다.
http://localhost:3000/videos 경로의 특정 주제, 세부 페이지를 보여주고 싶은 경우가 있는데요.
이때 http://localhost:3000/videos/videoDetail와 같이 videos URL 뒤에 id를 넣어주면됩니다. 특정 아이디 혹은 이름을 조회할 때 주로 사용합니다.
일단 URL parameters를 사용하기 위해 <VideoDetail /> 컴포넌트를 추가하고, 경로 주소는 변수 videos/:videoId처럼 받을 수 있게 작성합니다.
// App.js
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import './App.css';
import NotFound from './pages/NotFound';
import Videos from './pages/Videos/Videos';
import Root from './pages/Root/Root';
import Weather from './pages/Weather/Weather';
import Home from './pages/Home/Home';
import VideoDetail from './pages/VideoDetail/VideoDetail';
const router = createBrowserRouter ([
{
path: '/',
element: <Root />,
errorElement: <NotFound />,
children: [
{ index: true, element: <Home/>},
{ path: '/videos', element: <Videos />},
{ path: '/videos/:videoId', element: <VideoDetail />},
{ path: '/weather', element: <Weather />},
],
},
]);
function App() {
return <RouterProvider router={router} />;
}
export default App;
// videos.jsx
import React, { useState } from 'react';
import './Videos.css';
export default function Videos() {
const [text, setText] = useState('');
const handelChange = (e) => {
setText(e.target.value);
}
const handleSubmit = (e) => {
e.preventDefault();
setText('');
}
return <div className='videos'>Videos 페이지 입니다.
<form onSubmit={handleSubmit}>
<input type="text" placeholder='video id를 입력하세요' value={text} onChange={handelChange}/>
</form>
</div>;
}
이제 사용자가 해당 video id를 입력하고 enter를 치고 submit을 하면 해당 video 경로로 이동할 수 있습니다.

리액트 라우터에서 특정한 경로로 이동할 수 있는 방법은 두 가지 있습니다.
- 사용자가
<Link />태그에 명시된to='/'경로로 이동한다.- 리액트 라우터의
useNavigate()Hook을 사용해서 동적으로 이동한다.
// videos.jsx
import React, { useState } from 'react';
import './Videos.css';
import { useNavigate } from 'react-router-dom';
export default function Videos() {
const [text, setText] = useState('');
const navigate = useNavigate(); // 동적으로 이동
const handelChange = (e) => {
setText(e.target.value);
}
const handleSubmit = (e) => {
e.preventDefault();
setText('');
navigate(`/videos/${text}`);
}
return <div className='videos'>Videos 페이지 입니다.
<form onSubmit={handleSubmit}>
<input type="text" placeholder='video id를 입력하세요' value={text} onChange={handelChange}/>
</form>
</div>;
}

다음 화면

videos 페이지에서 videoId가 뭘로 입력됐는지 알 수 있습니다. 이때 사용하는 것이 useParams() 입니다.
// videoDetail.js
import React from 'react';
import './VideoDetail.css';
import { useParams } from 'react-router-dom';
export default function VideoDetail() {
const { videoId } = useParams();
return <div className='videoDetail'>videoDetail {videoId} 페이지입니당.</div>
}

console.log() 로 출력해보면 다음과 같습니다.
