자랑스러운 동료 SNUPI님과 함께 작성한 글입니다 :)
SNUPI의 티스토리에서도 동일한 내용을 확인하실 수 있습니다!
더더더더 자세한 내용은 역시 공식문서 확인!
제법 꼼꼼하게 잘 설명되어 있으니, 제 설명이 부족하다면 꼭 공식문서를 읽어보시길 바랍니다!
버전을 업데이트함으로써 번들 크기 최적화가 가능하다고 합니다 :) 70% 정도 줄어든대용
React Router v6은 React Hook을 많이 사용하므로 React Router v6으로 업그레이드를 시도하기 전에 React 16.8 이상에 있어야 합니다. 좋은 소식은 React Router v5가 React >= 15와 호환된다는 것입니다. 따라서 v5(또는 v4)를 사용 중이라면 라우터 코드를 건드리지 않고도 React를 업그레이드할 수 있어야 합니다. React 16.8로 업그레이드했으면 앱을 배포해야 합니다. 그런 다음 나중에 다시 돌아와서 중단한 부분부터 다시 시작할 수 있습니다.
React Router v6 makes heavy use of React hooks, so you'll need to be on React 16.8 or greater before attempting the upgrade to React Router v6. The good news is that React Router v5 is compatible with React >= 15, so if you're on v5 (or v4) you should be able to upgrade React without touching any of your router code.
Once you've upgraded to React 16.8, you should deploy your app. Then you can come back later and pick up where you left off.
$ yarn add history@5 react-router-dom@6
로 기존의 react-router-dom ver5 를 ver6로 업데이트 !
package.json
에서 기존 버전을 확인해볼 것 !
component={COM}
및 render={() => <h1>Hello<h1/>}
삭제)path="/Web/:id"
에서 path=":id"
로, 상대경로로 지정path="."
/ path=".."
등으로 상대경로를 표현한다// EX
function UserProfile() {
return (
<div>
<h2>
{/* This links to /users - the parent route */}
<Link to="..">All Users</Link>
</h2>
<h2>
{/* This links to /users/:id - the current route */}
<Link to=".">User Profile</Link>
</h2>
<h2>
{/* This links to /users/mj - a "sibling" route */}
<Link to="../mj">MJ</Link>
</h2>
</div>
);
}
기존 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;
*
을 사용합니다.Router.js
에서 중첩 라우터를 사용하고, 중첩 라우터에서 Outlet
컴포넌트 사용// Router.js
import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Web from "../Pages/Web";
import WebPost from "../Pages/WebPost";
const Router = () => {
return (
<BrowserRouter>
<Routes>
<Route path="web/*" element={<Web />}>
<Route path=":id" element={<WebPost />} />
</Route>
</Routes>
</BrowserRouter>
);
};
export default Router;
// Web.js
import React from "react";
import { Link, Routes, Route, Outlet } from "react-router-dom";
import WebPost from "./WebPost";
const Web = () => {
return (
<div>
<h1>This is Web</h1>
<ul>
<li>
<Link to="1">Post #1</Link>
</li>
<li>
<Link to="2">Post #2</Link>
</li>
<li>
<Link to="3">Post #3</Link>
</li>
<li>
<Link to="4">Post #4</Link>
</li>
</ul>
<Outlet />
</div>
);
};
export default Web;
export default Web;
// --------------------------------------------------------------------------------
// WebPost.js
import React from "react";
const WebPost = () => {
return <div>This is 포스트</div>;
};
export default WebPost;
Router.js
에서 자식 태그로 중첩하는 라우터를 기재하고,Web.js
에서 Outlet
라이브러리를 통해 가져옵니다./*
가 필수입니다.// Router.js
import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Web from "../Pages/Web";
const Router = () => {
return (
<BrowserRouter>
<Header />
<Routes>
<Route path="web/*" element={<Web />} />
</Routes>
</BrowserRouter>
);
};
export default Router;
// Web.js
import React from "react";
import { Link, Routes, Route} from "react-router-dom";
import WebPost from "./WebPost";
const Web = () => {
return (
<div>
<h1>This is Web</h1>
<ul>
<li>
<Link to="1">Post #1</Link>
</li>
<li>
<Link to="2">Post #2</Link>
</li>
<li>
<Link to="3">Post #3</Link>
</li>
<li>
<Link to="4">Post #4</Link>
</li>
</ul>
<Routes>
<Route path=":id" element={<WebPost />} />
</Routes>
</div>
);
};
export default Web;
// --------------------------------------------------------------------------------
// WebPost.js
import React from "react";
const WebPost = () => {
return <div>This is 포스트</div>;
};
export default WebPost;
Outlet
없이 곧바로 Routes
, Route
로 기재할 수 있습니다.pathname
가져와 styled-components와 결합 - useLocationimport 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;
useLocation
Hook을 사용하여 pathname을 받아올 수 있습니다.:id
path 이용하기 - useParams// WebPost.js
import React from "react";
import { useParams } from "react-router";
const WebPost = () => {
const { id } = useParams();
return <div>#{id}번째 포스트</div>;
};
export default WebPost;
useParams
Hook을 이용하여 :id
값을 받아옵니다.기존의 react-router-config가 useRoutes라는 Hook으로 변경되었습니다. 패키지를 추가로 설치해야했던 것과는 달리 useRoutes라는 훅으로 routes를 구성할 수 있게 되었습니다.
react-router-config의 기본 사용
routes라는 배열에 사용할 컴포넌트를 할당하여 사용합니다.
child의 child도 정의할 수 있습니다. 자식 컴포넌트들을 더 렌더링해야하는 경우에 renderRoutes를 사용하는데, 이 때 전달되는 파라미터는 아래와 같이 3가지입니다.
// react-router-config
// yarn add react-router-config로 설치 후 사용
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")
);
v6부터는 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;
}
위에서 언급한 Routes와 요사한 것으로 보이는데, 실제로 <Routes>
는 useRoutes
를 감싼 wrapper라고 공식문서에서 언급하고 있습니다. 공식문서에서는 <Routes>
와 useRoutes
모두를 권장하며, 둘 중 자신이 더 선호하는 것을 사용하면 됩니다. React Router says, "Honestly, we like and use them both."
useHistory를 아시나여! Link태그와 유사한 작업을 할 수 있게 도와주는 훅입니다.
아래와 같이 useHistory를 사용하면, URL의 끝에 /home을 추가함으로써 페이지 이동이 가능했습니다.
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부터는 navigate라는 명칭을 사용합니다. history.push와 history.replace 모두 navigate라는 명칭으로 사용합니다.
이상의 코드는 다음과 같이 변경됩니다.
import { useNavigate } from "react-router-dom";
function App() {
let navigate = useNavigate();
function handleClick() {
navigate("/home");
}
return (
<div>
<button onClick={handleClick}>go home</button>
</div>
);
}
만약 replace 기능이 필요하다면, navigate(to, { replace: true })
의 형태로 사용할 수 있습니다. state를 사용한다면 navigate(to, { state })
의 형태로 사용할 수 있습니다. 여기서 to
는 <Link>
에서 사용한 to=''
와 동일한 내용을 넣으면 됩니다.
useHistory의 기능 중 { go, goBack, goForward }
는 각각 해당 위치로, 이전으로, 다음으로
의 역할을 수행해왔는데, 이 부분도 navigate로 통일하고 index를 넣음으로써 해결합니다.
// v5
import { useHistory } from "react-router-dom";
function App() {
const { go, goBack, goForward } = useHistory();
return (
<>
<button onClick={() => go(-2)}>
Go 2 pages back
</button>
<button onClick={goBack}>Go back</button>
<button onClick={goForward}>Go forward</button>
<button onClick={() => go(2)}>
Go 2 pages forward
</button>
</>
);
}
// v6
import { useNavigate } from "react-router-dom";
function App() {
const navigate = useNavigate();
return (
<>
<button onClick={() => navigate(-2)}>
Go 2 pages back
</button>
<button onClick={() => navigate(-1)}>Go back</button>
<button onClick={() => navigate(1)}>
Go forward
</button>
<button onClick={() => navigate(2)}>
Go 2 pages forward
</button>
</>
);
}
이러한 변화의 가장 주된 이유는 React suspense와의 호환성을 더 높이기 위함입니다. 아직 이전의 클릭이 로딩 중인 상태에서 다른 라우트로의 링크를 클릭한 경우와 같이 pending이 충돌되는 경우에 더 부드러운 경험(smoother experience)를 제공할 수 있습니다. navigate API는 이전의 pending 작업을 알아차리고 해당 내용을 history stack에 PUSH하는 것이 아니라 REPLACE함으로써 로드되지 않은 기록으로 끝나지 않도록 합니다.
<NavLink exact>
가 아닌 <NavLink end>
로!<NavLink
to="/messages"
~~- style={{ color: 'blue' }}~~
~~- activeStyle={{ color: 'green' }}~~
+ style={({ isActive }) => ({ color: isActive ? 'green' : 'blue' })}
>
Messages
</NavLink>
<NavLink
to="/messages"
~~- className="nav-link"
- activeClassName="activated"~~
+ className={({ isActive }) => "nav-link" + (isActive ? " activated" : "")}
>
Messages
</NavLink>
react-router-dom
에서 react-router-dom/server
로 번들이 이동되었습니다.<Link>
의 component prop 제거
감사합니다 정말 보기 쉽게 정리해주셨네요