Frontend/Package

React Router 핵심 개념과 버전 | react-router-dom

신림쥐 2024. 8. 22. 13:43
728x90

 

     


    React Router

    • React Router는 React를 위한 다중 전략 라우터입니다.
    • react-router-dom 라이브러리를 통해 React 애플리케이션에서 주로 사용합니다.
    • React 애플리케이션에서 여러 페이지를 쉽게 관리하고 내비게이션을 구현할 수 있습니다.
    <Routes>
      <Route index element={<Home />} />
      <Route path="about" element={<About />} />
    
      <Route element={<AuthLayout />}>
        <Route path="login" element={<Login />} />
        <Route path="register" element={<Register />} />
      </Route>
    
      <Route path="concerts">
        <Route index element={<ConcertsHome />} />
        <Route path=":city" element={<City />} />
        <Route path="trending" element={<Trending />} />
      </Route>
    </Routes>

     

     

    React Router 버전별 특징

    • v5 : v4의 안정성을 높힌 버전, v5이상 부터 모던 프론트엔드에 적합한 버전이라고 한다.
    • v6 : 구조적 개선이 진행, v7을 데이터 패칭 강화를 위한 마련한 버전이다.
    • v7 : v6을 유지하면서 성능 및 데이터 패칭을 강화한 버전이다.
      React Router v4 이하 React Router v5 React Router v6 React Router v7
    배포이력
    • 최초 2016년 이전 출시
    • 2017-03-16 : v4.0.0 출시
    • 2019-03-14 : v5.0.0 출시
    • 2021-11-03 : v6.0.0이 출시
    • 2025-02-27 : v6.30.0 출시
      • 7버전 출시후에도 6버전 병행 유지중
    • 2024-11-21 : v7.0.0 출시
    • 2025-03-06 : v7.3.0 출시 (lastest 25.3.19)
    리액트
    최소 버전
    • React 0.14 이상 지원
    • React >= 15, React <= 16.7
    • React >= 12.2.0, React >=16.8 (React Hook 지원 필수)
    • node@20, react@18, react-dom@18 필요
    하위
    호완성
    • v3 → v4 호환성 깨짐
    • v4 → v5 호환성 유지
    • 라우터 코드 수정 없이 React 16.7까지 업데이트 가능
    • v5 → v6 호환성 깨짐
    • v6 → v7 호환성 유지
    • installGlobals()를 통한 fetch 폴리필 지원 중단
    주요 변경 사항
    1. routeConfig 기반 객체 선언 방식 사용
    2. browserHistory, hashHistory 분리됨
    3. matchRoutes() 방식으로 동적 라우팅 처리
    4. this.props.route 방식으로 라우트 정보 접근
    1. 기존 라우팅 방식을 JSX 기반으로 변환 (객체 선언 방식 제거 됨)
    2. <Switch> 도입으로 첫 번째 매칭 라우트만 렌더링
    3. useHistory, useParams 등의 훅 추가
    4. BrowserRouter, HashRouter 통합
    1. <Routes><Outlet> 추가로 중첩 라우팅 개선
    2. useNavigateuseHistory 대체
    3. loader 기능 추가로 데이터 Fetching 지원
    4. exact 제거 (자동 경로 매칭)
    1. v6 구조를 유지하면서 Fetching과 Suspense 기능 개선
    2. loader 방식 강화 및 성능 최적화
    3. React Suspense 기반 데이터 관리 지원
    4. v6에서 v7로 업그레이드 시 깨지는 변경 사항 없음

     

    지원 모드

    • React Router에서 얼마나 많은 제어나 도움을 원하는지에 따라 모드를 선택할 수 있습니다.
    • Declarative에서 Data로, 그리고 Framework로 이동하면 아키텍처 제어를 희생하고 더 많은 기능이 추가됩니다.
      • 선언적 모드
      • 데이터 모드
      • 프레임워크 모드

    선언적 모드

    • 구성요소 매칭, 탐색, 활성화 상태 등 기본 라우터 기능만 간단하게 React Router를 사용하고 싶습니다
    • 보류 중인 상태(로컬 우선, 백그라운드 데이터 복제/동기화 등)를 건너뛰거나 해당 상태에 대한 자체 추상화가 있는 데이터 계층이 있습니다.
    • Create React App에서 나옵니다(프레임워크 모드도 고려할 수 있음)
    • v6에서 왔고 만족합니다.<BrowserRouter>
    import React from "react";
    import ReactDOM from "react-dom/client";
    import { BrowserRouter } from "react-router";
    import App from "./app";
    
    ReactDOM.createRoot(root).render(
      <BrowserRouter>
        <Routes>
          <Route path="/" element={<App />} />
        </Routes>
      </BrowserRouter>
    );

    데이터 모드

    • React 렌더링 외부로 경로 구성을 이동한 아키텍처입니다.
    • action와 같은 데이터 API를 사용하여 데이터 로딩, 작업, 보류 중인 상태 등을 추가할 수 있습니다.
    • 데이터와 관련된 기능 외 번들링, 데이터 및 서버 추상화에 대한 제어 기능도 지원합니다.
    • v6.4에서 데이터 라우터를 시작했고 만족합니다.
    import React from "react";
    import ReactDOM from "react-dom/client";
    import { createBrowserRouter, RouterProvider } from "react-router";
    import App from "./app";
    
    let router = createBrowserRouter([
      {
        path: "/",
        Component: Root,
        loader: loadRootData,
      },
    ]);
    
    ReactDOM.createRoot(root).render(
      <RouterProvider router={router} />
    );

    프레임워크 모드

    • Framework Mode는 Vite 플러그인으로 Data Mode를 래핑하여 다음과 같은 React Router 전체 환경을 추가합니다.
      • Remix, Next.js, Solid Start, SvelteKit, Astro, TanStack Start, typesafe 등을 사용중
      • SPA, SSR 및 정적 렌더링 전략
      • 지능형 코드 분할
    import { index, route } from "@react-router/dev/routes";
    
    export default [
      index("./home.tsx"),
      route("about", "./about.tsx"),
    ];

     

    데이터 모드로 언제 이동하나요?

    • 원래 Remix Data API를 React Router로 가져오기 시작했을 때 , 우리는 그것이 경로를 구성하는 꽤 다른 방식을 가져온다는 것을 깨달았습니다.
    • React가 구성 요소 트리를 렌더링할 때<Routes> 구성 요소 를 통해 경로를 발견하는 대신, 우리는 페칭을 렌더링에서 분리할 수 있도록 경로 정의를 들어올려야 했습니다 . 이러하면서 BrowserRouter이로 인해 흥미로운 난제가 발생했습니다.
    • 데이터 라우터를 통해 정의된 경로에서만 작동하는 소수의 새로운 데이터 API를 사용할 수 있다.
      • 예) loader, action, shouldRevalidate, handle, 및 와 같은 경로 수준 데이터 APIlazy
      • useLoaderData, useActionData, useFetcher, useMatches, useNavigation, 등과 같은 구성 요소 내 데이터 후크
      • route.errorElement, route.ErrorBoundary, 및 와 같은 오류 처리 APIuseRouteError

    <BrowserRouter>에서 <RouterProvider>로 마이그레이션 방법

    1. <BrowerRouter> 구성 요소를 사용하는 프로젝트 준비

    import {
      BrowserRouter,
      Link,
      Route,
      Routes,
    } from "react-router-dom";
    
    export default function App() {
      return (
        <BrowserRouter>
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/blog/*" element={<BlogApp />} />
            <Route path="/users/*" element={<UserApp />} />
          </Routes>
        </BrowserRouter>
      );
    }

     

    2. 루트 스플랫 경로로 RouterProvider 추가

    import {
      createBrowserRouter,
      Link,
      Route,
      RouterProvider,
      Routes,
    } from "react-router-dom";
    
    // 3️⃣ Router singleton created
    const router = createBrowserRouter([
      { path: "*", element: <Root /> },
    ]);
    
    // 4️⃣ RouterProvider added
    export default function App() {
      return <RouterProvider router={router} />;
    }
    
    // 1️⃣ Changed from App to Root
    function Root() {
      // 2️⃣ `BrowserRouter` component removed, but the <Routes>/<Route>
      // component below are unchanged
      return (
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/blog/*" element={<BlogApp />} />
          <Route path="/users/*" element={<UserApp />} />
        </Routes>
      );
    }

     

    3. <Route>정의를 데이터 라우터까지 올리는 것

    const router = createBrowserRouter([
      { path: "/", element: <Home /> },
      {
        path: "/blog/*",
        children: [
          { index: true, element: <h1>Blog Index</h1> },
          { path: "*", element: <BlogApp /> },
        ],
      },
      { path: "*", element: <Root /> },
    ]);
    
    export default function App() {
      return <RouterProvider router={router} />;
    }
    
    function Root() {
      return (
        <Routes>
          {/* ⬆️ Home route lifted up to the data router */}
          <Route path="/blog/*" element={<BlogApp />} />
          <Route path="/users/*" element={<UserApp />} />
        </Routes>
      );
    }

     

    React Router 주요 구성요소

    • BrowserRouter: HTML5의 History API를 사용하여 URL을 관리하는 라우터입니다.
    • Routes: Route들을 래핑하여 경로와 해당 경로에 매핑되는 컴포넌트를 정의합니다.
    • Route: 특정 경로에 대해 렌더링할 컴포넌트를 정의합니다.
    • Link: 사용자가 클릭할 수 있는 링크를 제공합니다. 이 링크를 클릭하면 페이지가 새로 고침 없이 변경됩니다.

     

    React Router 후크

    useNavigate

    useLocation

    • useLocation는 현재 location객체를 반환합니다. 현재 위치가 변경될 때마다 어떤 부작용을 수행하고 싶을 때 유용할 수 있습니다.
    속성 설명
    location.hash 현재 URL의 해시(# 이후의 부분).
    location.key 이 위치의 고유 키 (주로 React Router에서 사용).
    location.pathname 현재 URL의 경로 (도메인 이후부터 쿼리 문자열 이전까지의 부분).
    location.search 현재 URL의 쿼리 문자열 (? 포함, 키-값 쌍의 리스트).
    location.state 또는 navigate에 의해 전달된 상태 값. (React Router에서 상태 전송 시 사용).
    declare function useLocation(): Location;
    
    interface Location<State = any> extends Path {
      state: State;
      key: string;
    }
    
    interface Path {
      pathname: string;
      search: string;
      hash: string;
    }

     

    지원 기능

    라우팅 - 중첩된 경로

    • 기본 라우터 설정
    <Routes>
      <Route path="dashboard" element={<Dashboard />}>
        <Route index element={<Home />} />
        <Route path="settings" element={<Settings />} />
      </Route>
    </Routes>
    • 페이지 설정
    // dashboard.tsx
    import { Outlet } from "react-router";
    
    export default function Dashboard() {
      return (
        <div>
          <h1>Dashboard</h1>
          {/* will either be <Home/> or <Settings/> */}
          <Outlet />
        </div>
      );
    }

    라우팅 - 레이아웃 경로

    • 기본 라우터 설정
    // 라우터
    <Routes>
      <Route element={<MarketingLayout />}>
        <Route index element={<MarketingHome />} />
        <Route path="contact" element={<Contact />} />
      </Route>
    
      <Route path="projects">
        <Route index element={<ProjectsHome />} />
        <Route element={<ProjectsLayout />}>
          <Route path=":pid" element={<Project />} />
          <Route path=":pid/edit" element={<EditProject />} />
        </Route>
      </Route>
    </Routes>

    라우팅 - 동적 세그먼트

    • prop 이 <Route path> 없는 A는 element부모 레이아웃을 도입하지 않고 자식 경로에 경로 접두사를 추가합니다.
    <Route path="projects">
      <Route index element={<ProjectsHome />} />
      <Route element={<ProjectsLayout />}>
        <Route path=":pid" element={<Project />} />
        <Route path=":pid/edit" element={<EditProject />} />
      </Route>
    </Route>
    • 경로 세그먼트가 :다음으로 시작하면 "동적 세그먼트"가 됩니다. 경로가 URL과 일치하면 동적 세그먼트가 URL에서 구문 분석되어 . params와 같은 다른 라우터 API에 제공됩니다 useParams.
    <Route
      path="/c/:categoryId/p/:productId"
      element={<Product />}
    />
    • 하나의 경로 경로에 여러 개의 동적 세그먼트를 포함할 수 있습니다.
    import { useParams } from "react-router";
    
    export default function CategoryProduct() {
      let { categoryId, productId } = useParams();
      // ...
    }

    페이지 탐색

    1. <NavLink>
    2. <Link>
    3. <Form>
    4. redirect
    5. useNavigate

    찾을 수 없는 페이지

    • 존재하지 않는 경로에 대한 처리를 위해 찾을 수 없는 페이지를 설정
    ├─src/
    │  ├─components/
    │  ├─src/
    │  │  ├─layouts/
    │  │  │  └─Default.tsx
    │  │  ├─pages/
    │  │  │  ├─main.tsx
    │  │  │  └─NotFound.tsx
    │  │  └─index.tsx
    import NotFound from './NotFound'
    
    <Routes>
      <Route path="dashboard" element={<Dashboard />}>
        <Route index element={<Main/>} />
        <Route path="*" element={<NotFound/>} />
      </Route>
    </Routes>

    인증된 페이지

    선언적 모드 문서 : https://reactrouter.com/6.30.0/route/route

    • 승인된 상태에서만 접근 할 수 있는 페이지 설정
    ├─src/
    │  ├─components/
    │  ├─src/
    │  │  ├─loaders/
    │  │  │  └─requiresAuth.ts
    │  │  ├─pages/
    │  │  │  ├─SignIn.tsx
    │  │  │  └─Mypage.tsx
    │  │  └─index.tsx
    • 사용자가 페이지에 접근하면, 페이지를 렌더링하기 전에 Loader 함수를 통해 승인 여부를 확인
    • Loader 함수는 페이지 접근 시 호출되는 로더(Loader) 함수로 접근 페이지의 요청 정보(request)를 받아서 사용한다.
    async function getUser() {
      const token = localStorage.getItem('access_token')
      return token ? true : false'
    }
    
    export async function requiresAuth({ request }: { request: Request }) {
      const user = await getUser() // 인증 여부를 확인하는 함수
      
      if (!user) {
        return redirect(`/signin`) // 돌아갈 페이지로 이동
      }
      return user // 원하는 반환값 처리...
    }
    import SignIn from './pages/SignIn'
    import { requiresAuth } from './loaders/requiresAuth'
    
    const router = createBrowserRouter([
      {
        element: <DefaultLayout />,
        children: [
          // ...
          {
            path: '/signin',
            element: <SignIn />
          },
          {
            path: '/mypage',
            element: <Mypage />,
            loader: requiresAuth,
            children: [
              {
                path: ':info',
                element: <Myinfo />
              }
            ]
          }
          // ...
        ]
      }
    ])
    
    export default function Router() {
      return <RouterProvider router={router} />
    }

    스크롤 복원

    • 사용자가 뒤로 혹은 앞으로 가기를 할 때 페이지의 스크롤 위치를 복원하거나, 새로운 페이지의 스크롤 위치를 취상단으로 자동으로 처리
    • <ScrollRestoration> 컴포넌트 사용는 최상위 레이아웃에 한 번만 추가하면 됩니다.
    import { Outlet, ScrollRestoration } from 'react-router-dom'
    import TheHeader from '@/components/TheHeader'
    
    export default function DefaultLayout() {
      return (
        <>
          <TheHeader />
          <Outlet />
          <ScrollRestoration />
        </>
      )
    }

     

    react-router-dom v5 -> v6 마이그레이션

    • Node.js 12.2.0 이상과 호환, React 16.8 이상 버전에서 사용이 가능
    • 가장 최적화된 성능은 Node.js 14 이상, React 18과 함께 사용하는 것
    npm install react-router-dom@6

     

     

    1. Switch가 Routes로 대체

    Routes는 경로에 맞는 첫 번째 Route만 렌더링

    import { Route, Switch } from 'react-router-dom';
    <Switch>
      <Route path="/home" component={Home} />
      <Route path="/about" component={About} />
    </Switch>
    import { Route, Routes } from 'react-router-dom';
    <Routes>
      <Route path="/home" element={<Home />} />
      <Route path="/about" element={<About />} />
    </Routes>

     

    2. withRouter 지원 안함

    useNavigate, useLocation, useParams, useMatch 같은 훅으로 대체

    import { withRouter } from 'react-router-dom';
    class MyComponent extends React.Component {
      componentDidMount() {
        const { history, location, match } = this.props;
        console.log(history, location, match);
      }
      render() {
        return <div>My Component</div>;
      }
    }
    export default withRouter(MyComponent);
    import { useNavigate, useLocation, useParams } from 'react-router-dom';
    const MyComponent = () => {
      const navigate = useNavigate();
      const location = useLocation();
      const params = useParams();
      console.log(navigate, location, params);
      return <div>My Component</div>;
    };
    export default MyComponent;

     

    3. useHistory가 useNavigate로 대체

    import { useHistory } from 'react-router-dom';
    const MyComponent = () => {
      const history = useHistory();
      const goToHome = () => {
        history.push('/home'); // 페이지 이동
        history.push('/home', { userId: 123 }); // 페이지 이동 및 상태 전달
        history.replace(RouteLink.ROOT); // 페이지 대체
        history.goBack(); // 이전 페이지로 돌아가기
      };
      return <button onClick={goToHome}>Go Home</button>;
    };
    import { useNavigate } from 'react-router-dom';
    const MyComponent = () => {
      const navigate = useNavigate();
      const goToHome = () => {
        navigate('/home'); // 페이지 이동
        navigate('/home', { state: { userId: 123 } }); // 페이지 이동 및 상태 전달
        navigate(RouteLink.ROOT, { replace: true }); // 페이지 대체
        navigate(-1); // 이전 페이지로 돌아가기
      };
      return <button onClick={goToHome}>Go Home</button>;
    };

     

    상태읽기(React Router v5, v6동일)

    import { useLocation } from 'react-router-dom';
    const Home = () => {
      const location = useLocation();
      const state = location.state as { userId: number };
      return <div>User ID: {state?.userId}</div>;
    };
    export default Home;

     

     

    4. RouteComponentProps 지원 안함

    useParams 같은 훅으로 대체

    import { RouteComponentProps } from 'react-router-dom';
    interface MyProps extends RouteComponentProps<{ id: string }> {}
    const MyComponent: React.FC<MyProps> = ({ match }) => {
      const { id } = match.params;
      return <div>ID: {id}</div>;
    };
    import { useParams } from 'react-router-dom';
    const MyComponent: React.FC = () => {
      const { id } = useParams<{ id: string }>();
      return <div>ID: {id}</div>;
    };

     

    5. 중첩 라우트(Nested Routes)

    • Route 내부에서 설정
    <Switch>
      <Route path="/dashboard" component={Dashboard} />
      <Route path="/dashboard/settings" component={Settings} />
    </Switch>
    <Routes>
      <Route path="/dashboard" element={<Dashboard />}>
        <Route path="settings" element={<Settings />} />
      </Route>
    </Routes>

     

    6. Route 속성값 변경 및 삭제

    • render -> element
      • render 대신 element 속성을 사용하여 JSX를 직접 전달합니다.
    • <>로 Fragment 래핑
      • 여러 요소를 반환할 때 React.Fragment(<>)를 사용합니다.
    • NotFound 컴포넌트
      • <Route path={RouteLink.DEFAULT} render={() => <NotFound />} />에서 element={<NotFound />}로 수정했습니다.
    • exac 명시 안함
    • useParams path 표시는 ':id'로 v5버전과 동일
    <Route path="/user/:id" component={User} />
    <Route path="/user/:id" element={<User />} />

     

    728x90