[React Router/lib.] React Router v6
오랜만에 React 학습을 진행하는데, React Router가 6버전이 출시되었다고 한다.
기존에 사용하던 문법들이 일부 수정된 것을 감안하여, 이를 한번 훑고자 짧게나마 포스팅을 적는다.
💙 개요
React Router 6버전은, 기존의 5버전에 비해 React 최신문법에 걸맞도록 업데이트 되었다고 공식문서는 소개한다.
특히, React Hooks가 적용되었기에, 이를 사용하려면 React v16.8 이상을 우선 설치해야하며, 5 -> 6버전으로 마이그레이션을 진행하면 된다.
React Router 6버전은 번들 사이즈가 5버전에 비해 약 70%가 감소하였으며, 이는 App 빌드시 큰 이점이 될 것이다.
💙 주요 변경사항
1. <Switch> ➡️ <Routes> 로 변경
- <Route> 요소들을 포함하는 <Routes>로 Wrapper 명칭변경
- <Route>는 component 및 render에 화살표 함수를 사용하지 않고, element 속성에 JSX 컴포넌트를 전달해준다.
- exact 옵션이 삭제되었다. 기본적으로 exact가 적용되며, 모든 하위 path에서 라우팅을 하고 싶다면 path 끝에 *을 붙인다.
- path에 ":id"와 같은 하위경로, "." 및 ".." 와 같은 상대경로도 지정이 가능하다.
// 기존(v5)
import { BrowserRouter, Route, Switch } from "react-router-dom";
import Home from "./pages/Home";
import Write from "./pages/Write";
function App() {
return (
<BrowserRouter>
<Switch>
<Route path="/" component={() => <Home />} />
<Route exact path="/write" component={() => <Write />} />
<Route component={() => <div>Page Not Found</div>} />
</Switch>
</BrowserRouter>
);
}
export default App;
// 변경(v6)
import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { Main, Page1, Page2, NotFound } from "../pages";
import { Header } from ".";
const Router = () => {
return (
<BrowserRouter>
<Header />
<Routes>
<Route path="/" element={<Main />} />
<Route path="/page1/*" element={<Page1 />} />
<Route path="/page2/*" element={<Page2 />} />
<Route path="/*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
};
export default Router;
그래서, 기존에 App.tsx를 Routes.tsx로 명명하던 관례를 원복하면 된다.
2. 간편해진 중첩 라우팅
기존에는 중첩 라우팅을 위해서는 페이지, 컨테이너 컴포넌트 각각을 <Switch>로 랩핑하고 내부에 <Route> 컴포넌트를 돌려야 했다.
특히, 컨테이너 컴포넌트는 useRouteMatch() Hooks를 통해 path를 받고 여기에 하위path를 연결시켜야만 했다.
6버전에서는 페이지 단에서 모든 경로를 중첩하고, 중첩영역 렌더링 요소는 <Outlet> 컴포넌트로 그려서 이중작업을 방지할 수 있다.
(이렇게 하면, 통상 App.tsx 최상단의 <Routes> 내에서 모든 라우팅 구조를 중첩할 수 있어 직관적이게 된다.)
// 기존(v5)
import { BrowserRouter, Switch, Route, Link, useRouteMatch } from 'react-router-dom';
// App(루트) Component
function App() {
return (
<BrowserRouter>
<Switch>
<Route path="/user" component={User} />
</Switch>
</BrowserRouter>
);
}
// Page Component
function User() {
const { path } = useRouteMatch();
return (
<div>
<Switch>
<Route path={`${path}/detail`}>
<UserDetail />
</Route>
</Switch>
</div>
);
}
import { BrowserRouter, Routes, Route, Outlet } from 'react-router-dom';
// App(루트) Component : Route 집약
function App() {
return (
<BrowserRouter>
<Routes>
<Route path='user' element={<User />} >
<Route path='detail' element={<UserDetail />} />
</Route>
</Routes>
</BrowserRouter>
);
}
// Page Component : <Outlet> 활용
function User() {
return (
<>
<Outlet />
</>
)
}
3. useLocation, useParams - Router Parameter 사용
Routing을 통해 진입한 페이지의 파라미터를 참조하기 위한 Hooks로 useLocation과 useParams를 제공한다.
useLocation은 pathname(정적 경로), useParams는 :id(동적 경로 혹은 파라미터)를 참조하는데 사용된다.
// useLocaiton
import React from "react";
import { Link, useLocation } from "react-router-dom";
import styled from "styled-components";
const HeaderWrapper = styled("header")`
margin-bottom: 30px;
`;
const List = styled("ul")`
display: flex;
`;
const Item = styled("li")`
margin-right: 20px;
text-transform: uppercase;
font-weight: 600;
color: ${(props) => (props.selected ? "white" : "black")};
background-color: ${(props) => (props.selected ? "#f1c40f" : "white")};
`;
const Header = () => {
const { pathname } = useLocation();
return (
<HeaderWrapper>
{/* header 태그 */}
<List>
{/* ul 태그 */}
<Item selected={pathname.startsWith("/web")}>
{/* li 태그 */}
<Link to="/web">Go to Web</Link>
</Item>
<Item selected={pathname === "/design"}>
<Link to="/design">Go to Design</Link>
</Item>
<Item selected={pathname === "/server"}>
<Link to="/server">Go to Server</Link>
</Item>
</List>
</HeaderWrapper>
);
};
export default Header;
// useParams
import React from "react";
import { useParams } from "react-router";
const WebPost = () => {
const { id } = useParams();
return <div>#{id}번째 포스트</div>;
};
export default WebPost;
4. useRoutes
기존의 react-router-config가 useRoutes Hooks 으로 대체되었다. (패키지 설치가 불필요해짐)
기존엔, routes 배열을 정의하여 여기에 컴포넌트를 배치하고, renderRoutes 메서드로 Root(App) 컴포넌트에 반영할 수 있다.
// 기존(v5)
import { renderRoutes } from "react-router-config";
const routes = [
{
component: Root,
routes: [
{
path: "/",
exact: true,
component: Home
},
{
path: "/child/:id",
component: Child,
routes: [
{
path: "/child/:id/grand-child",
component: GrandChild
}
]
}
]
}
];
const Root = ({ route }) => (
<div>
<h1>Root</h1>
{/* 자식 라우트들이 렌더할 수 있도록 renderRoutes 실행 */}
{renderRoutes(route.routes)}
</div>
);
const Home = ({ route }) => (
<div>
<h2>Home</h2>
</div>
);
const Child = ({ route }) => (
<div>
<h2>Child</h2>
{/* renderRoutes가 없으면 자식들은 렌더되지 않음 */}
{renderRoutes(route.routes)}
</div>
);
const GrandChild = ({ someProp }) => (
<div>
<h3>Grand Child</h3>
<div>{someProp}</div>
</div>
);
ReactDOM.render(
<BrowserRouter>
{/* renderRoutes에 가장 처음 정의했던 routes 자체를 뿌려줌으로써 차례로 렌더링될 수 있도록 함 */}
{renderRoutes(routes)}
</BrowserRouter>,
document.getElementById("root")
);
이 패턴을, 버전6부터는 useRoutes Hooks를 통해 직접 Root(App)에 라우터를 배치할 수 있다.
<Route>의 중첩 라우팅, useRoutes 둘 다 공식문서에서 권장하는 방법으로 기호에 따라 사용하면 된다.
function App() {
let element = useRoutes([
// Route에서 사용하는 props의 요소들과 동일
{ path: "/", element: <Home /> },
{ path: "dashboard", element: <Dashboard /> },
{
path: "invoices",
element: <Invoices />,
// 중첩 라우트의 경우도 Route에서와 같이 children이라는 property를 사용
children: [
{ path: ":id", element: <Invoice /> },
{ path: "sent", element: <SentInvoices /> }
]
},
// NotFound 페이지는 다음과 같이 구현할 수 있음
{ path: "*", element: <NotFound /> }
]);
// element를 return함으로써 적절한 계층으로 구성된 element가 렌더링 될 수 있도록 함
return element;
}
5. useHistory ➡️ useNavigate
버전5까지 history.push, history.replace 를 했던 useHistory Hooks가 useNavigate로 대체되었다.
// 기존(v5)
import { useHistory } from "react-router-dom";
function App() {
let history = useHistory();
function handleClick() {
history.push("/home");
}
return (
<div>
<button onClick={handleClick}>go home</button>
</div>
);
}
// 변경(v6)
import { useNavigate } from "react-router-dom";
function App() {
let navigate = useNavigate();
function handleClick() {
navigate("/home");
}
return (
<div>
<button onClick={handleClick}>go home</button>
</div>
);
}
6. 이외 변경사항
- <NavLink exact> 가 <NavLink end> 로 변경 (현재 활성화된 라우터)
- props 명칭들의 renaming
- activeClassNAme, activeStyle props 제거 => style, className에 함수 사용 가능
- StaticRouter가 react-router-dom/server 번들로 이동 (SSR 라우팅)
- <Link>의 component prop 제거
React를 오랜만에 사용하면서, Router 설정에서 오류 메세지가 많이 발생하였고 그 원인이 버전 변경에 걸맞는 문법이 아니어서였다.
기능들보다는 컴포넌트나 props 명칭들 위주로 변경되었으며, 중첩 라우팅과 useRoutes() 가 직관적으로 개선되어 Router 구조를 가시화하는데에는 큰 진전이 있었다고 느껴진다!
[출처]
- [React Router] 공식문서 : https://reactrouter.com/docs/en/v6/upgrading/v5
- [v6 변경사항] soryeongk 님의 블로그 : https://velog.io/@soryeongk/ReactRouterDomV6
- [v6 변경사항] woolta 님의 블로그 : https://blog.woolta.com/categories/1/posts/211