ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [React Router/lib.] React Router v6
    Front-End(Web)/React - 프레임워크(React, Next) 2021. 12. 14. 22:55
    반응형

    오랜만에 React 학습을 진행하는데, React Router가 6버전이 출시되었다고 한다.

    기존에 사용하던 문법들이 일부 수정된 것을 감안하여, 이를 한번 훑고자 짧게나마 포스팅을 적는다.


    💙 개요

    React Router 6버전은, 기존의 5버전에 비해 React 최신문법에 걸맞도록 업데이트 되었다고 공식문서는 소개한다.

    특히, React Hooks가 적용되었기에, 이를 사용하려면 React v16.8 이상을 우선 설치해야하며, 5 -> 6버전으로 마이그레이션을 진행하면 된다.

     

    React Router 6버전은 번들 사이즈가 5버전에 비해 약 70%가 감소하였으며, 이는 App 빌드시 큰 이점이 될 것이다.

    출처 : https://blog.woolta.com/categories/1/posts/211

     

    💙 주요 변경사항

     

    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을 통해 진입한 페이지의 파라미터를 참조하기 위한 HooksuseLocation과 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-configuseRoutes 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 HooksuseNavigate로 대체되었다.

    // 기존(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  

     

    반응형
Designed by Tistory.