IT/React

React Router Dom - V6 업그레이드 되면서 달라진 것들

라임웨일 2022. 7. 25. 10:11
반응형

 

React Router Dom의 버전이 V6로 변경되면서 이전에 사용되던 Hooks나 기능들이 변경되었습니다.  보다 자세한 내용은 역시 공식문서를 확인하는 게 가장 좋지만 프로젝트를 진행하면서 바로 동작이 가능하려면 우선 무엇이 변경되었는지 알아야 하겠죠?

이번에 정리를 통해 알아보도록 할게요!  출바알!🎶

👍 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

또는

npm i 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;

// --------------------------------------------------------------------------------
// 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 사용은 ?

4-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을 받아올 수 있습니다.

 

4-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공식문서로..!! Go Go~

 

출처: https://velog.io/@soryeongk/ReactRouterDomV6

반응형