
기존 웹사이트는 새로운 페이지로 이동하기 위해서 웹 서버로 문서를 요청 후 받아와 렌더링하는 형식으로 작동했다. 그래서 많은 처리 시간이 필요했는데, CSR를 사용하는 React Router를 이용하면 한 페이지 내에서도 여러 페이지를 이동하듯 동작할 수 있다. 이 과정에서 웹 서버에 새로운 문서를 요청하지 않았음에도 URL이 업데이트되는데, 서버에 문서를 요청하는 대신 필요한 부분에 대한 데이터 요청을 할 것이다.
// ./shared/Router.js
import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "../pages/Home";
import About from "../pages/About";
import Contact from "../pages/Contact";
import Works from "../pages/Works";
const Router = () => {
return (
<BrowserRouter>
<Routes>
{/*
Routes안에 라우팅할 페이지들을 넣는다.
Route에는 react-router-dom에서 지원하는 props들이 있다
path : 페이지로 접근할 경로
element : 해당 주소로 이동했을 때 보여주고자 하는 컴포넌트
*/}
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
<Route path="works" element={<Works />} />
</Routes>
</BrowserRouter>
);
};
export default Router;
// App.jsx
import React from "react";
import Router from "./shared/Router"; // 위에서 만든 라우터 불러오기
function App() {
return <Router />;
}
export default App;
react-router-dom HooksuseNavigate이벤트 핸들러에 navigate를 사용하면 우리가 보내고자 하는 path로 페이지를 이동시킬 수 있다.
// src/pages/home.js
import { useNavigate } from "react-router-dom";
const Home = () => {
// useNavigate 훅에서 제공하는 기능들을 navigate를 통해 사용가능
const navigate = useNavigate();
return (
<button
onClick={() => {
navigate("/works");
}}
>
works로 이동
</button>
);
};
export default Home;
useLocation현재 페이지의 여러 정보를 얻을 수 있는 Hook. 이 정보들을 이용하여 조건부 렌더링에 사용하는 등 여러 용도로 활용가능하다.
// src/pages/works.js
import { useLocation } from "react-router-dom";
const Works = () => {
const location = useLocation();
console.log("location :>> ", location);
// { hash: "", key: "~", pathname: "/works", search: "", state: null }
return (
<div>
<div>{`현재 페이지 : ${location.pathname.slice(1)}`}</div>
</div>
);
};
export default Works;
LinkLink는 Hook은 아니다. 그러나 알고 있어야 하는 API다.
Link는 a태그를 대체하는 API로, JSX에서는 무조건 a 대신 Link를 사용해야 한다. a태그는 페이지를 이동하면서 브라우저가 새로고침이 되는데, 그 과정에서 모든 컴포넌트가 리렌더링 되어 모든 상태값들이 초기화되기 때문이다.
import { Link, useLocation } from 'react-router-dom';
const Works = () => {
const location = useLocation();
return (
<div>
<div>{`현재 페이지 : ${location.pathname.slice(1)}`}</div>
<Link to="/contact">contact 페이지로 이동하기</Link>
</div>
);
};
export default Works;
props.children을 활용한 레이아웃 구성모든 페이지들이 공통으로 사용하는 레이아웃이 있는 경우 children을 활용하여 공통 레이아웃을 만들 수 있다.
// Router.jsx
...
const Router = () => {
return (
<BrowserRouter>
<Layout> // Layout 태그 내부가 props.children으로 Layout 컴포넌트로 전달된다.
<Routes>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
<Route path="works" element={<Works />} />
</Routes>
</Layout>
</BrowserRouter>
);
};
// Layout.jsx
...
function Layout({ children }) {
return (
<div>
<Header />
<div style={{...layoutStyles}}>
{children}
</div>
<Footer />
</div>
);
}
...
path에 유동적인 값을 넣어서 특정 페이지로 이동하도록 만드는 방법을 동적 라우팅이라 한다.
const Router = () => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
// 일반 라우팅
<Route path="works" element={<Works />} />
// 동적 라우팅
<Route path="works/:id" element={<Works />} />
</Routes>
</BrowserRouter>
);
};
일반 라우팅과 다르게 동적 라우팅은 :id라는 동적인 값을 받는다. 그래서 "works/1"로 이동하든 "works/42"로 이동하든 "works/pikachu"로 이동하든 모두 <Works />로 이동하게 된다.
이 :id는 useParams Hook에서 조회할 수 있는 값이 되어 페이지마다 표시되는 화면을 다르게 만들 수 있다.
🪄동적 라우팅을 사용하더라도 여전히 문서는 하나다! SPA!
useParamsuseParams는 상술했듯 path에 있는 id값을 조회할 수 있게 해주는 Hook이다.
// src/pages/Work.js
import React from 'react';
import { useParams } from 'react-router-dom';
const data = [
{ id: 1, todo: '리액트 배우기' },
{ id: 2, todo: '노드 배우기' },
{ id: 3, todo: '자바스크립트 배우기' },
{ id: 4, todo: '파이어 베이스 배우기' },
{ id: 5, todo: '넥스트 배우기' },
{ id: 6, todo: 'HTTP 프로토콜 배우기' },
];
function Work() {
const param = useParams();
// 현재 페이지에 따라 param이 결정된다. 만약 지금 works/6 페이지에 있다면
// param.id = "6", param.todo = 'HTTP 프로토콜 배우기' 가 될 것이다.
const work = data.find((work) => work.id === parseInt(param.id));
return <div>{work.todo}</div>;
}
export default Work;
(Outlet)중첩 라우팅은 특정 라우트 내에서 추가적인 라우트를 정의하는 방식을 말한다. 여러 계층의 UI를 구성할 때 많이 쓰인다.
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import DashboardLayout from './DashboardLayout';
import Profile from './Profile';
import Settings from './Settings';
import Reports from './Reports';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<Profile />} />
<Route path="settings" element={<Settings />} />
<Route path="reports" element={<Reports />} />
</Route>
</Routes>
</BrowserRouter>
);
}
/dashboard 경로는 DashboardLayout 컴포넌트를 사용한다. 이 레이아웃 내에서 /dashboard/settings, /dashboard/reports 등의 경로로 추가적인 페이지들을 중첩하여 설정할 수 있다! 이렇게 DashboardLayout 컴포넌트 안에서 각 섹션의 독립적인 라우트 관리가 가능하며, UX를 개선할 수 있다.
다만 사용하기 위해서는 부모 컴포넌트 DashboardLayout에서 Outlet을 설정해줘야 한다.
import { Outlet } from 'react-router-dom';
function DashboardLayout() {
return (
<div>
<header>Header Section</header>
<main>
<Outlet /> {/* 여기에 자식 라우트의 컴포넌트가 렌더링된다! (Settings, Reports) */}
</main>
<footer>Footer Section</footer>
</div>
);
}
children을 사용한 공통 레이아웃둘 모두 비슷한 동작을 하지만, 적용 범위에서 차이가 있다.
중첩 라우팅을 사용하면 각 라우트마다 고유의 Layout을 적용할 수 있는 반면, 공통 Layout 방식은 모든 라우트에 동일한 레이아웃을 적용한다. 그래서 후자는 Layout 컴포넌트가 Routes의 자식으로 직접 포함되지 않고, 라우트 구성 외부에서 사용된다.
따라서 중첩 라우팅은 더 동적이고 유연한 라우트 관리를 제공한다는 장점이, 공통 레이아웃 방식은 구조가 단순하고 일관되어 관리가 용이하다는 장점이 있다.