이전에는 URL을 통해 동적으로 데이터를 전달하는 방법과 이를 사용하기 위한 2가지 훅에 대해서 살펴봤다.
이번 포스팅에서는 React Router의 중요한 기능 중 하나인 중첩 라우팅(Nested Routing)에 대해 살펴보자 👀
중첩 라우팅은 라우트 안에 다시 라우트를 품는 구조를 말한다.
중첩 라우팅은 특히 URL 구조가 계층적일 때 이를 컴포넌트 구조에도 자연스럽게 반영할 수 있도록 도와준다.
조금 더 자세히 살펴보자
다음과 같은 URL 구조를 가진 웹 애플리케이션이 있다고 가정해보자
URL 구조
/user
/user/profile
/user/settings
/user/settings/notification
/user/settings/privacy
이런 계층적인 구조를 단순 라우팅으로 구현한다면 어떻게 될까?
위 구조를 코드로 표현하면 다음과 같다.
Sample Code
// App.jsx function App() { return ( <Routes> <Route path="/user" element={<UserHome />} /> <Route path="/user/profile" element={<UserProfile />} /> <Route path="/user/settings" element={<Settings />} /> <Route path="/user/settings/notification" element={<Notification />} /> <Route path="/user/settings/privacy" element={<Privacy />} /> </Routes> ); }
물론 이렇게 라우팅을 구성하더라도 기능상의 문제는 없다.
하지만, 개발자는 이러한 구조에서 다음과 같은 문제점을 발견할 수 있다.
문제점
- 모든 라우트를 한 곳에서 관리해야 함
- URL 구조의 계층이 코드에 잘 드러나지 않음
- 공통 레이아웃이나 로직을 처리하기 어려움
이러한 문제를 중첩 라우팅을 사용해서 해결할 수 있다.
중첩 라우팅은 <Route>
컴포넌트를 중첩하여 사용하고, <Outlet/>
컴포넌트로 자식 컴포넌트의 렌더링 위치를 지정한다.
간단한 사용 예시는 다음과 같다.
(일반 라우팅 예제 를 단순히 중첩 라우팅을 사용해서 변경해봤다.)
Sample Code
// App.jsx function App() { return ( <Routes> <Route path="/user" element={<UserHome />}> <Route path="profile" element={<UserProfile />} /> <Route path="settings" element={<Settings />} /> <Route path="settings/notification" element={<Notification />} /> <Route path="settings/privacy" element={<Privacy />} /> </Route> </Routes> ); }
// UserHome.jsx function UserHome() { return ( <div> <h1>UserHome</h1> <Outlet /> </div> ); }
// UserProfile.jsx function UserProfile() { return <div>UserProfile</div>; }
Result View
~~/user/profile
이처럼 중첩 라우팅을 이용하여 컴포넌트들을 구성할 수 있다.
마찬가지로 ~~/user/profile
에 접근하면 <UserProfile/>
컴포넌트가 렌더링된다.
그런데 위 화면에서는 하나 이상한 게 있다.
아마 ~~/user/profile
으로 접근하면 <UserProfile/>
컴포넌트만 렌더링되는 것을 기대할 것이다.
하지만, 위에서는 <UserHome/>
컴포넌트와 <UserProfile/>
컴포넌트 모두 렌더링되는 것을 볼 수 있다.
여기서 우리는 <Outlet/>
컴포넌트에 대한 이해가 필요하다.
<Outlet/>
컴포넌트<Outlet/>
컴포넌트는 부모 Route가 감싸고 있는 자식 Route의 컴포넌트를 렌더링하는 역할을 한다.
중요한 것은 URL 변경이 발생해야 자식 Route의 컴포넌트를 렌더링한다는 것을 기억하자
우선 사용 예시를 먼저 살펴보자
Sample Code
// App.jsx export default function App() { return ( <BrowserRouter> <ul> <li> <Link to="/">Go Home</Link> </li> <li> <Link to="/profile">Go Profile</Link> </li> <li> <Link to="/setting">Go Setting</Link> </li> </ul> <Routes> <Route path="/" element={<Home />}> <Route path="profile" element={<Profile />} /> <Route path="setting" element={<Setting />} /> </Route> </Routes> </BrowserRouter> ); }
const Home = () => { return ( <div> <h1>Home</h1> <Outlet /> </div> ); }; export default Home;
const Profile = () => { return <div>Profile</div>; }; export default Profile;
const Setting = () => { return <div>Setting</div>; }; export default Setting;
Result View
위 코드를 분석하면 다음과 같다.
<Home/>
컴포넌트가 부모 Route의 element로 설정되어 있다.
그 안에 <Profile/>
과 <Setting/>
컴포넌트를 자식 Route로 래핑하고 있다.
따라서 사용자가 ~/profile 또는 ~/setting으로 접근하면?
- 먼저 부모인
<Home/>
컴포넌트가 렌더링된다- URL과 매칭되는 자식 Route의 컴포넌트를 찾는다
- 매칭된 컴포넌트를
<Home/>
내부의<Outlet/>
위치에 렌더링한다
이 과정을 통해 <h1>Home</h1>
은 항상 보이면서, 그 아래 <Outlet/>
위치에서만 페이지 전환이 일어난다.
이렇게 중첩 라우팅을 사용하면 공통 레이아웃을 유지하면서 내부 콘텐츠만 변경할 수 있다!
중첩 라우팅은 복잡한 라우트 구조를 체계적으로 관리할 수 있게 해준다.
특히 컴포넌트와 함께 사용하면 레이아웃 공유와 코드 구조화를 효과적으로 달성할 수 있다.
다음 포스팅에서는 이번에 살펴본 을 응용한 공통 레이아웃을 구현해보려고 한다 😁