React에서 라우팅 처리에 대해 배워보자.
React Router 버전 6.4부터 지원하는, 라우터 객체를 만들어서 쓰는 방법에 대해 알아보자.
이 새로운 라우팅 시스템은 이전 버전의 BrowserRouter와는 다른 방식으로 동작하며,
라우팅을 관리하고 조작하는 더 직관적인 API를 제공합니다.
현재 App 컴포넌트는 2가지 역할을 하고 있다. 어떤 레이아웃을 취하고 있는지
와 어떻게 라우팅이 되는지
이다.
export default function App({products}: {
products: Product[];
}) {
return (
<div>
<Header />
<div>
<Routes>
<Route path='/' element={<HomePage/>}/>
<Route path='/about' element={<AboutPage />}/>
</Routes>
</div>
<Footer />
</div>
);
}
App 컴포넌트에서 이 역할들을 독립시켜보자.
routes 객체를 만든다. 경로와 경로에 맞는 컴포넌트를 맵핑한 객체를 가진다.
const routes = [
{path: '/', element: <HomePage />},
{path: '/about', element: <AboutPage />},
];
createBrowserRouter를 이용해서 라우터 객체를 생성한다. 이렇게 생성된 라우터 객체는 라우팅을 처리하는데 사용한다.
const router = createBrowserRouter(routes);
그리고 RouterProvider를 이용해서 router 객체를 쓰겠다고 한다. 이렇게 하면 애플리케이션 전체에서 라우팅을 처리할 수 있게 된다.
export default function App({products}: {
products: Product[];
}) {
return (
<RouterProvider router={router} />
);
}
더 이상 BrowserRouter가 라우팅을 관리하지 않는다. createBrowserRouter 함수와 RouterProvider 컴포넌트를 사용하여 라우팅을 설정하고 관리한다.
때문에 이전에 App 컴포넌트 밖에서 BrowserRouter로 잡아준 것은 지운다.
const routes = [
{path: '/', element: <HomePage />},
{path: '/about', element: <AboutPage />},
];
const router = createBrowserRouter(routes);
export default function App({products}: {
products: Product[];
}) {
return (
<RouterProvider router={router} />
);
}
레이아웃 자체를 다음과 같이 컴포넌트로 독립시킨다.
function Layout() {
return (
<div>
<Header />
<main>
<Outlet />
</main>
<Footer />
</div>
);
}
routes한테 Layout 컴포넌트에 적용한다는 것을 잡아줘야 한다. 그려지는 것은 모두 <Layout />
으로 그려지도록 해야 한다.
이를 위해서 routes를 계층형으로 바꾸었다.
다만 Layout 컴포넌트가 HomePage 컴포넌트와 AboutPage 컴포넌트를 인지해서 올바른 위치에 넣어야 한다. 이는 React Router가 지원하는 컴포넌트인 Outlet 을 쓰면된다.
Outlet 컴포넌트는 Layout 컴포넌트 안에 정의된 레이아웃 구조를 그대로 유지하면서 컴포넌트를 동적으로 렌더링 할 수 있다.
const routes = [
{
element: <Layout />,
children: [
{path: '/', element: <HomePage />},
{path: '/about', element: <AboutPage />},
],
},
];
function Layout() {
return (
<div>
<Header />
<main>
<Outlet />
</main>
<Footer />
</div>
);
}
const routes = [
{
element: <Layout />,
children: [
{path: '/', element: <HomePage />},
{path: '/about', element: <AboutPage />},
],
},
];
const router = createBrowserRouter(routes);
export default function App({products}: {
products: Product[];
}) {
return (
<RouterProvider router={router} />
);
}
routes는 테스트 때 필요한 정보이다. 그래서 App.tsx 파일에서 분리하는게 가져다쓰기에 좋다.
- src
- components
- Footer.tsx
- Header.tsx
- Layout.tsx (추가)
- pages
- AboutPage.tsx
- HomePage.tsx
- App.tsx
- main.tsx
- routes.tsx (추가)
App 컴포넌트가 하는 일이 줄었다. 이 일은 main 컴포넌트에 보내도 되는 일이라서 App 컴포넌트가 필요 없어졌다.
const router = createBrowserRouter(routes);
export default function App({products}: {
products: Product[];
}) {
return (
<RouterProvider router={router} />
);
}
그래도 App 컴포넌트를 살려는 두겠다.
이전에는 App.test.tsx로 App 컴포넌트를 테스트했다.
이제는 routes가 컴포넌트와 레이아웃에 대한 정보를 모두 들고 있다. 때문에 routes만 테스트 하면 된다.
renderRouter
라는 테스트 헬퍼 함수를 만들어서 중복되는 부분을 제거했다.
createMemoryRouter의 두 번째 인자의 객체의 프로퍼티로 initialEntries을 잡을 수 있다.
import {render, screen} from '@testing-library/react';
import {RouterProvider, createMemoryRouter} from 'react-router-dom';
import routes from './routes';
const context = describe;
describe('App', () => {
function renderRouter(path: string) {
const router = createMemoryRouter(routes, {initialEntries: [path]});
render(<RouterProvider router={router} />);
}
context('when the current path is “/”', () => {
it('renders the home page', () => {
renderRouter('/');
screen.getByText(/Hello, World/);
});
});
context('when the current path is “/about”', () => {
it('renders the about page', () => {
renderRouter('/about');
screen.getByText(/This is test/);
});
});
});
라우터 객체를 생성하는 것이 BrowserRouter를 이용해서 라우팅을 관리했을 때보다 더 직관적이고 구조화된 코드를 작성할 수 있었다.
그래서 테스트도 컴포넌트가 아닌 routes 에만 할 수 있었다.
페이지 전환을 하게 되면 전체를 새로고침한다. 완전히 새로 불러오게 된다.
전체를 새로 부르지 않고 일부만 처리해서 기존에 처리하던 것을 유지해보자.
유튜브도 음악이 재생 중이면, 다른 것을 눌러도 음악이 꺼지지 않는다. 배경에서 음악이 계속 나온다.