라우팅이란 사용자가 요청한 URL에 따라 알맞는 페이지를 보여주는 것으로 웹애플리케이션이 커지게 되면 하나의 페이지로 구성하기가 매우 어렵습니다. 이럴 때 여러 페이지로 구성된 웹애플리케이션을 페이지별로 컴포넌트들을 분리해가면서 프로젝트를 관리하기 위해 필요한 것이 바로 라우팅 시스템입니다.
react-router-dom은 리액트의 라우팅 관련 라이브러리들 중에서 가장 많이 사용되고 있으며 컴포넌트 기반으로 라우팅 시스템을 설정할 수 있습니다.
간단하게 사용법을 알아 보도록 하겠습니다.
1. 프로젝트 생성 및 라이브러리 설치
| npx create-react-app router1 cd router1 npm i react-bootstrap bootstrap react-bootstrap-icons npm i dayjs holderjs npm i react-router-dom |
public 폴더의 모든 파일을 삭제하고 index.html 파일을 다음과 같이 만듭니다.
|
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>React App</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
|
src 폴더의 모든 파일을 지우고 App1.js파일을 다음과 같이 만듭니다.
|
function App1() {
return <h2>Hello World</h2>
}
export default App1;
|
index.js 파일을 다음과 같이 만듭니다.
|
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App1';
import { BrowserRouter } from 'react-router-dom';
document.querySelector("title").innerText = "react-router-dom tutorial";
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter future={{v7_startTransition: true, v7_relativeSplatPath:true}}>
<App />
</BrowserRouter>
</React.StrictMode>
);
|
document.querySelector("title").innerText = "react-router-dom tutorial";로 타이틀을 변경했습니다.
그리고 <App/> 컴포넌트를 <BrowserRouter></ BrowserRouter > 감싸 주시면 됩니다.
그러면 브라우저에 경고가 2개 나오는데 이때는 future 속성을 위와 같이 추가해주면 경고는 사라집니다.

각각의 페이지를 나타낼 컴포넌트들을 다음과 같이 만듭니다.
|
function Home(){
return (
<div>
<h2>
메인 화면입니다.
{` `}
<Link to="/">
<Button variant="outline-danger" size="sm">Home</Button>
</Link>
</h2>
</div>
);
}
function Page1(){
return (
<div>
<h2>
첫번째 페이지입니다.
{` `}
<Link to="/">
<Button variant="outline-danger" size="sm">Home</Button>
</Link>
</h2>
</div>
);
}
function Page2(){
return (
<div>
<h2>
두번째 페이지입니다.
{` `}
<Link to="/">
<Button variant="outline-danger" size="sm">Home</Button>
</Link>
</h2>
</div>
);
}
function NotFound() {
return (
<div>
<h2>
존재하지 않는 주소입니다.
{` `}
<Link to="/">
<Button variant="outline-danger" size="sm">Home</Button>
</Link>
</h2>
</div>
);
}
|
그리고 링크를 누르면 이동할 메뉴를 만듭니다.
이동할때는 <Link/>태그를 사용합니다.
| <Link to="이동할 주소">표시될 텍스트</Link> |
|
function Menu(){
return (
<nav>
<ul>
<li>
<Link to="/home">Home</Link>
</li>
<li>
<Link to="/page1">Page1</Link>
</li>
<li>
<Link to="/page2">Page2</Link>
</li>
<li>
<Link to="/notFound">NotFound</Link>
</li>
</ul>
</nav>
);
}
|
이제 주소를 직접 만들어 보겠습니다.
react-router-dom의 Routes와 Route 컴포넌트를 사용합니다. Routes안에 Route 컴포넌트를 사용합니다.
|
<Routes>
<Route path="만들주소 " element={표시될컴포넌트}/>
</Routes> |
<Route path="/" element={<Menu />}/> 는 /(root)에 Menu컴포넌트를 표시하겠다는 것입니다.
|
function App2() {
return (
<>
<Routes>
<Route path="/" element={<Menu />}/>
<Route path="/home" element={<Home />} />
<Route path="/page1" element={<Page1 />} />
<Route path="/page2" element={<Page2 />} />
<Route path="*" element={<NotFound />} />
</Routes>
</>
);
}
|
전체 소스 입니다.
App2.css
|
nav, div {
margin: 10px;
}
nav>ul>li {
list-style-type: none;
display: inline;
margin: 0px 10px;
}
|
App2.js
|
import { Button } from "react-bootstrap";
import { Link, Outlet, Route, Routes } from "react-router-dom";
import 'bootstrap/dist/css/bootstrap.min.css';
import './App2.css'
function Home(){
return (
<div>
<h2>
메인 화면입니다.
{` `}
<Link to="/">
<Button variant="outline-danger" size="sm">Home</Button>
</Link>
</h2>
</div>
);
}
function Page1(){
return (
<div>
<h2>
첫번째 페이지입니다.
{` `}
<Link to="/">
<Button variant="outline-danger" size="sm">Home</Button>
</Link>
</h2>
</div>
);
}
function Page2(){
return (
<div>
<h2>
두번째 페이지입니다.
{` `}
<Link to="/">
<Button variant="outline-danger" size="sm">Home</Button>
</Link>
</h2>
</div>
);
}
function NotFound() {
return (
<div>
<h2>
존재하지 않는 주소입니다.
{` `}
<Link to="/">
<Button variant="outline-danger" size="sm">Home</Button>
</Link>
</h2>
</div>
);
}
function Menu(){
return (
<nav>
<ul>
<li>
<Link to="/home">Home</Link>
</li>
<li>
<Link to="/page1">Page1</Link>
</li>
<li>
<Link to="/page2">Page2</Link>
</li>
<li>
<Link to="/notFound">NotFound</Link>
</li>
</ul>
</nav>
);
}
function App2() {
return (
<>
<Routes>
<Route path="/" element={<Menu />}/>
<Route path="/home" element={<Home />} />
<Route path="/page1" element={<Page1 />} />
<Route path="/page2" element={<Page2 />} />
<Route path="*" element={<NotFound />} />
</Routes>
</>
);
}
export default App2;
|
index.js파일의 다음만 변경합니다.
|
import App from './App1'; 를 import App from './App2';으로 변경합니다. |
다음과 같이 메뉴가 표시되며 각각의 링크를 클릭해서 각각의 컴포넌트 페이지로 이동이 가능합니다.

Home 버튼을 눌러 메뉴화면으로 복귀가 가능합니다.

주소 표시줄에 아무 주소나 입력하면 <NotFound/> 컴포넌트를 출력합니다.

메뉴바는 항상 나타나야 합니다. 이럴때는 중첩 라우팅을 이용하면 됩니다.
/ 밑으로 4개의 주소를 중첩해서 넣었습니다. 서브페이지들은 /를 빼고 작성합니다.
그러면 주소는 "상위라우터 Path/하위라우터 Path"형태로 만들어 집니다.
|
<Routes>
<Route path="/" element={<Menu />}>
<Route index element={<Home />} />
<Route path="page1" element={<Page1 />} />
<Route path="page2" element={<Page2 />} />
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
|
"<Route index element={} />" 하위메뉴를 선택하지 않았을때 기본적으로 보여줄 하위 컴포넌트를 지정하는것 입니다.
<Outlet/>으로 부모 라우트의 컴포넌트에서 자식 라우트 컴포넌트의 위치를 지정해 주어야 합니다.
|
function Menu(){
return (
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/page1">Page1</Link>
</li>
<li>
<Link to="/page2">Page2</Link>
</li>
<li>
<Link to="/notFound">NotFound</Link>
</li>
</ul>
<hr/> {/* 선추가 */}
<Outlet/> {/* 하위 컴포넌트가 표시될 위치 */}
</nav>
);
}
|
App3.js파일 전체입니다. 각각의 컴포넌트에서 버튼을 제거 했습니다.
|
import { Button } from "react-bootstrap";
import { Link, Outlet, Route, Routes } from "react-router-dom";
import 'bootstrap/dist/css/bootstrap.min.css';
import './App2.css'
function Home(){
return (
<div>
<h2>메인 화면입니다. </h2>
</div>
);
}
function Page1(){
return (
<div>
<h2>첫번째 페이지입니다. </h2>
</div>
);
}
function Page2(){
return (
<div>
<h2>두번째 페이지입니다. </h2>
</div>
);
}
function NotFound() {
return (
<div>
<h2>존재하지 않는 주소입니다.</h2>
</div>
);
}
function Menu(){
return (
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/page1">Page1</Link>
</li>
<li>
<Link to="/page2">Page2</Link>
</li>
<li>
<Link to="/notFound">NotFound</Link>
</li>
</ul>
<hr/>
<Outlet/>
</nav>
);
}
function App3() {
return (
<>
<Routes>
<Route path="/" element={<Menu />}>
<Route index element={<Home />} />
<Route path="page1" element={<Page1 />} />
<Route path="page2" element={<Page2 />} />
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
</>
);
}
export default App3;
|
index.js파일의 다음만 변경합니다.
|
import App from './App2'; 를 import App from './App3';으로 변경합니다. |
결과 화면입니다. 하단에 서브 페이지가 표시 됩니다. 기본 서브페이지는 index를 지정한 <Home/> 컴포넌트 입니다.

메뉴를 눌러 이동해 봅니다.

<Outlet/> 없이도 구현이 가능합니다.
다음과 같이 와일드카드를 이용하여 주소를 생성합니다. 하위에 주소가 존재한다고 지정해 주는것 입니다.
|
<Routes>
<Route path="/*" element={<Menu />}/>
</Routes>
|
그리고 보여줄 컴포넌트에 <Outlet/> 자리에 <Router>를 추가해 줍니다.
|
function Menu(){
return (
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/page1">Page1</Link>
</li>
<li>
<Link to="/page2">Page2</Link>
</li>
<li>
<Link to="/notFound">NotFound</Link>
</li>
</ul>
<hr/>
<Routes>
<Route index element={<Home />} />
<Route path="page1" element={<Page1 />} />
<Route path="page2" element={<Page2 />} />
<Route path="*" element={<NotFound />} />
</Routes>
</nav>
);
}
|
완성된 App4.js파일입니다.
|
import { Button } from "react-bootstrap";
import { Link, Outlet, Route, Routes } from "react-router-dom";
import 'bootstrap/dist/css/bootstrap.min.css';
import './App2.css'
function Home(){
return (
<div>
<h2>메인 화면입니다. </h2>
</div>
);
}
function Page1(){
return (
<div>
<h2>첫번째 페이지입니다. </h2>
</div>
);
}
function Page2(){
return (
<div>
<h2>두번째 페이지입니다. </h2>
</div>
);
}
function NotFound() {
return (
<div>
<h2>존재하지 않는 주소입니다.</h2>
</div>
);
}
function Menu(){
return (
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/page1">Page1</Link>
</li>
<li>
<Link to="/page2">Page2</Link>
</li>
<li>
<Link to="/notFound">NotFound</Link>
</li>
</ul>
<hr/>
<Routes>
<Route index element={<Home />} />
<Route path="page1" element={<Page1 />} />
<Route path="page2" element={<Page2 />} />
<Route path="*" element={<NotFound />} />
</Routes>
</nav>
);
}
function App3() {
return (
<>
<Routes>
<Route path="/*" element={<Menu />}/>
</Routes>
</>
);
}
export default App3;
|
index.js파일의 다음만 변경합니다.
|
import App from './App3'; 를 import App from './App4';으로 변경합니다. |
결과 화면은 같습니다.


useParams(), useLocation(), useSearchParams()
useParams()
/comments : 전체 목록보기 /comments/1 : 1번글 보기 /comments/2 : 2번글 보기 /comments/3 : 3번글 보기 |
위와 같이 처리하기 위해서는 useParam()을 이용합니다.
라우터를 다음과 같이 정의 합니다.
|
<Route path="comments" element={<Comments />} >
<Route path=":id" element={<Comment />} />
</Route>
|
그럼 Comment 에서 다음과 같이 하면 id값을 확인할 수 있습니다.
|
// URL 파라미터 사용하기
const params = useParams();
console.log(`파라메터 : ${JSON.stringify(params)}`);
console.log(`파라메터 : ${params.id}`);
|
/comments/1 요청시 params.id 값은 1이 됩니다.
이 id값을 이용하여 원하는 데이터만 찾아서 처리를 하면 id값마다 다른 처리가 가능합니다.
useLocation()
페이지 정보를 처리할떄는 useLocation()을 이용합니다.
|
const pageInfo = useLocation();
console.log(JSON.stringify(pageInfo));
console.log(pageInfo.search); // Query String 확인
|
다음과 같은 정보를 확인할 수 있습니다. 여기에서 search키를 확인하면 요청한 QueryString을 확인할 수 있습니다.
| {"pathname":"/hanjas/102","search":"?show=false","hash":"","state":null,"key":"zfy8bgps"} ?show=false |
useSearchParams()
하지만 위의 search는 문자열이므로 또 다시 파싱을 해야 합니다.
예를 들어 "?a=100&b=200&c=300"의 경우 각각 a에는 100을 b에는 200을 c에는 300을 보낸것인데 이것을 파싱해 줘야 합니다. 이것을 점눈으로 담당하는 useSearchParams()을 이용하면 편리합니다.
다음과 같이 하면 각각의 변수에 각각의 값을 얻을 수 있습니다. 하지만 타입이 무조건 string이므로 필요에 따라 값을 형변환 해서 사용해야 합니다.
|
// Query String 사용
const [searchParams, setSearchParams] = useSearchParams();
console.log(typeof(searchParams) + " : " + searchParams);
for(let key of searchParams.keys()){
console.log(key + " : " + searchParams.get(key));
}
?a=100&b=200&c=300 요청 시 a = 100 b = 200 c = 300 으로 처리가 가능합니다. |
그럼 사자성어 20개를 가지고 실습을 진행해 보도록 하겠습니다.
Data.js 파일을 다음과 같이 만듭니다.
|
export const HANJA = [
{"id": 101, "h":"萬死一生","k":"만사일생","t":"만 번의 죽을 고비에서 살아난다는 말로, 겨우 죽음을 모면하는 것"},
{"id": 102, "h":"三間草家","k":"삼간초가","t":"세 칸짜리 초가집이라는 뜻으로 작고 보잘 것 없는 집."},
{"id": 103, "h":"二人同心","k":"이인동심","t":"두 사람의 마음이 하나가 됨. 절친한 친구 사이"},
{"id": 104, "h":"一字千金","k":"일자천금","t":"아주 빼어난 글이나 시문"},
{"id": 105, "h":"敎學相長","k":"교학상장","t":"가르치고 배우면 서로가 성장한다."},
{"id": 106, "h":"九十春光","k":"구십춘광","t":"노인의 마음이 청년같이 젊음을 이름"},
{"id": 107, "h":"男女老少","k":"남녀노소","t":"남자와 여자, 늙은이와 젊은이. 모든 사람."},
{"id": 108, "h":"門前成市","k":"문전성시","t":"권세가의 문 앞이 방문객으로 몹시 붐빔"},
{"id": 109, "h":"百年大計","k":"백년대계","t":"먼 뒷날까지 걸친 큰 계획"},
{"id": 110, "h":"白面書生","k":"백면서생","t":"글만 읽고 세상일에 경험이 없는 사람"},
{"id": 111, "h":"三生有幸","k":"삼생유행","t":"세 번 태어날 행운이 있다는 뜻으로 지극히 운 좋음"},
{"id": 112, "h":"先行後敎","k":"선행후교","t":"선인의 행위를 들어 후학을 가르침."},
{"id": 113, "h":"自問自答","k":"자문자답","t":"제가 묻고 제가 대답함."},
{"id": 114, "h":"作心三日","k":"작심삼일","t":"한번 결심한 것이 사흘을 가지 않음. 곧 결심이 굳지 못함"},
{"id": 115, "h":"長生不死","k":"장생불사","t":"오랫동안 살아 죽지 아니함."},
{"id": 116, "h":"千年一淸","k":"천년일청","t":"黃河같은 濁流가 맑아지기를 천년 동안 바란다. 가능하지 않은 일을 바라는 것."},
{"id": 117, "h":"靑山流水","k":"청산유수","t":"청산에 흐르는 물. 거침없이 잘하는 말에 비유."},
{"id": 118, "h":"八方美人","k":"팔방미인","t":"여러 방면의 일에 두루 능통한 사람"},
{"id": 119, "h":"風月主人","k":"풍월주인","t":"맑은 바람과 밝은 달을 벗하여 노는 한가한 사람. 자연 풍경을 좋아하는 사람."},
{"id": 120, "h":"木人石心","k":"목인석심","t":"의지가 굳어 어떠한 유혹에도 마음이 흔들리지 않는 사람"}
];
|
사자성어 목록을 보여줄 컴포넌트를 만듭니다.
Hanjas.js파일을 다음과 같이 만듭니다.
|
import { NavLink, Outlet } from 'react-router-dom';
import {HANJA} from './Data'
const Hanjas = ()=>{
return (
<>
<div className='box'>
{
HANJA.map((hanja, index)=>{
let content = `${hanja.k}`;
let no = `${index+1}`.padStart(3, 0) + '. '
return (
<div key={hanja.h} >
{no}
{/*
<Link key={hanja.h} to={`/hanjas/${hanja.id}`}>{content}</Link> <br/>
*/}
{/* Link 대신 NavLink 컴포넌트를 사용하면 링크에서 사용하는 경로가
현재 라우트의 경로와 일치하는 경우 특정 CSS를 적용할 수 있습니다. */} <NavLink
to={`/hanjas/${hanja.id}?show=false`}
style={
({ isActive }) => {
return { background: isActive ? "silver" : "", };
}
}
>
{content}
</NavLink>
</div>
)
})
}
</div>
<div>
<Outlet/>
</div>
</>
)
}
export default Hanjas;
|
왼쪽의 사자성어를 클릭하면 id값을 가지고 가서 오른쪽에 사자성어에 대한 설명을 표시하도록 만들었습니다.
App2.css 파일을 수정합니다.
|
nav, div {
margin: 5px;
}
nav>ul>li {
list-style-type: none;
display: inline;
margin: 0px 10px;
}
.box {
width: 130px;
float: left;
}
|
1개의 사자성어를 표시할 컴포넌트를 만듭니다.
Hanja.js 파일을 다음과 같이 만듭니다.
|
import { useLocation, useParams, useSearchParams } from 'react-router-dom';
import { HANJA } from './Data'
import { Button } from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
const Hanja = ()=>{
// URL 파라미터 사용하기
const params = useParams();
// console.log(`파라메터 : ${JSON.stringify(params)}`);
// console.log(`파라메터 : ${params.id}`);
// 넘어온 아이디에 해당하는 한자를 찾는다.
const hanja = HANJA.find((h)=>h.id===parseInt(params.id));
// console.log(hanja);
const pageInfo = useLocation();
// console.log(JSON.stringify(pageInfo));
// console.log(pageInfo.search); // Query String 확인
// Query String 사용
const [searchParams, setSearchParams] = useSearchParams();
/*
console.log(typeof(searchParams) + " : " + searchParams);
for(let key of searchParams.keys()){
console.log(key + " : " + searchParams.get(key));
}
*/
let show = searchParams.get('show')==='true';
console.log(`${typeof(show)} : ${show}`);
let title = show ? '설명 숨기기' : '설명 보이기';
const clickHandler = (e)=>{
let text = e.target.innerText;
if(text==='설명 보이기'){
e.target.innerText = '설명 숨기기';
show = true;
setSearchParams({show : true});
}else{
e.target.innerText = '설명 보이기';
show = false;
setSearchParams({show : false});
}
}
return (
<div>
<h2>
{hanja.h}({hanja.k})
{` `}
<Button variant='outline-success' size="sm" onClick={clickHandler}>{title}</Button>
</h2>
{show ? <h2>{hanja.t}</h2> : ""}
</div>
)
}
export default Hanja;
|
파라메터로 id값을 받아 사자성어 목록 중 원하는 사자 성어만 얻어옵니다.
그리고 Query String으로 show 값을 받아 true이면 설명을 보이고 false이면 설명을 숨기는 기능을 버튼에 추가해 보았습니다.
이제 App5.js파일을 수정해서 결과를 확인해 보도록 하겠습니다.
|
import { Link, Outlet, Route, Routes } from "react-router-dom";
import './App2.css'
import Hanjas from "./Hanjas";
import Hanja from "./Hanja";
function Home(){
return (
<div>
<h2>메인 화면입니다. </h2>
</div>
);
}
function Page1(){
return (
<div>
<h2>첫번째 페이지입니다. </h2>
</div>
);
}
function NotFound() {
return (
<div>
<h2>존재하지 않는 주소입니다.</h2>
</div>
);
}
function Menu(){
return (
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/page1">Page1</Link>
</li>
<li>
<Link to="/hanjas">사자성어</Link>
</li>
<li>
<Link to="/notFound">NotFound</Link>
</li>
</ul>
<hr/>
<Outlet/>
</nav>
);
}
function App5() {
return (
<>
<Routes>
<Route path="/" element={<Menu />}>
<Route index element={<Home />} />
<Route path="page1" element={<Page1 />} />
<Route path="hanjas" element={<Hanjas />} >
<Route path=":id" element={<Hanja />} />
</Route>
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
</>
);
}
export default App5;
|
index.js파일의 다음만 변경합니다.
|
import App from './App4'; 를 import App from './App5';로 변경합니다. |
결과 화면은 다음과 같습니다.




이렇게 react-router-dom에 대하여 간단히 살펴 보았습니다.
'Front End > React' 카테고리의 다른 글
| [React Exam] react-router-dom 사용해보기 2 (0) | 2024.11.21 |
|---|---|
| [React Exam 41] useState()에 대하여 알아보기 (1) | 2024.11.06 |
| [React Exam 40] React Hook 알아보기 (0) | 2024.11.06 |
| [React Exam 39] 가위바위보 게임 만들기 (1) | 2024.11.05 |
| [React Exam 38] 로또 번호 자동 생성기 만들기 2 (0) | 2024.11.05 |