[React] react-router-dom v6 업그레이드 되면서 달라진 것들

soryeongk·2021년 11월 12일
188

JavaScript

목록 보기
6/7
post-thumbnail

자랑스러운 동료 SNUPI님과 함께 작성한 글입니다 :)
SNUPI의 티스토리에서도 동일한 내용을 확인하실 수 있습니다!

더더더더 자세한 내용은 역시 공식문서 확인!
제법 꼼꼼하게 잘 설명되어 있으니, 제 설명이 부족하다면 꼭 공식문서를 읽어보시길 바랍니다!
버전을 업데이트함으로써 번들 크기 최적화가 가능하다고 합니다 :) 70% 정도 줄어든대용

0. React v16.8

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.

1. 설치방법

$ yarn add history@5 react-router-dom@6

로 기존의 react-router-dom ver5 를 ver6로 업데이트 !

package.json 에서 기존 버전을 확인해볼 것 !

2. Switch 대신 Routes 를 쓸 때의 변경사항

  • Switch 의 네이밍이 Routes로 변경되었습니다 :)
  • exact 옵션 삭제
  • component 방식 변경 (component={COM}render={() => <h1>Hello<h1/>} 삭제)
  • path 를 기존의 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>
  );
}
  • Switch 는 알다시피, 경로가 적합한 처음 한 컴포넌트를 찾아주었는데,
    여기서 발생하는 (적합한 url의 순서를 뒤로 지정해주어 발생하는) 버그를 방지합니다.

예시

기존 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;
  • Switch를 사용합니다.
  • exact로 복수의 라우팅을 막습니다.
  • component={} 내에 arrow function을 사용하여 component를 전달합니다.

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;
  • exact 는 더이상 사용하지 않고 여러 라우팅을 매칭하고 싶은 경우 URL 뒤에 * 을 사용합니다.
  • component 대신 elemet로 바로 component를 전달합니다.

3. 중첩 라우팅

방법 1) 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 라이브러리를 통해 가져옵니다.
  • exact 안 쓰는 대신 /*가 필수입니다.

방법 2) 곧바로 기재

// 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로 기재할 수 있습니다.

4. props 사용은 ?

1) pathname 가져와 styled-components와 결합 - useLocation

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;
  • 위 코드와 같이 useLocation Hook을 사용하여 pathname을 받아올 수 있습니다.

2) :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 값을 받아옵니다.

5. useRoutes

기존의 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."

6. useHistory대신 useNavigate

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함으로써 로드되지 않은 기록으로 끝나지 않도록 합니다.

7. 이외의 변경사항

  • <NavLink exact>가 아닌 <NavLink end>로!
  • 다른 라이브러리의 일반적인 관행과 더 일치하도록 prop의 이름이 renaming 되었습니다 :)
  • activeClassName, activeStyle props 제거
  • 대신 style과 className에 함수를 전달할 수 있게 되었습니다.
<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>
  • import { StaticRouter } from "react-router-dom/server"
  • StaticRouter가 react-router-dom에서 react-router-dom/server로 번들이 이동되었습니다.
  • <Link>의 component prop 제거
    • 이제 더이상 에서 component prop을 지원하지 않습니다.
    • 자세한 내용은 React Router공식문서로!!
profile
웹 프론트엔드 개발자 령이의 어쩌구 저쩌구

8개의 댓글

comment-user-thumbnail
2021년 12월 9일

감사합니다 정말 보기 쉽게 정리해주셨네요

답글 달기
comment-user-thumbnail
2022년 1월 19일

좋은 정보 감사합니다 :)

답글 달기
comment-user-thumbnail
2022년 2월 3일

감사합니다 덕분에 해맸는데 해결했습니다!

답글 달기
comment-user-thumbnail
2022년 5월 9일

너무 감사합니다 덕분에 변경 점을 한눈에 볼 수 있어서 공부 잘 하고 갑니다.

답글 달기
comment-user-thumbnail
2022년 6월 12일

오우 내용 너무좋네요

답글 달기
comment-user-thumbnail
2022년 6월 17일

내용 넘 좋네요. 감사합니다.

답글 달기
comment-user-thumbnail
2022년 7월 15일

와 왜 바뀐거지 ㅠ.ㅠ 기존게 더 좋았던 1인..

답글 달기
comment-user-thumbnail
2022년 8월 9일

감사합니다 ^오^b

답글 달기