티스토리 뷰

728x90

최근에 진행했던 프로젝트에서 React Router와 Tanstack Query를 사용하여 애플리케이션 라우터를 설정했다. 오늘은 이를 적용하며 공부한 내용 중 React Router를 위주로 정리해보려고 한다.

 

React Router v6.4에는 아래와 같은 라우터들이 추가되었다. 아래 라우터와 함께 action / errorElement / lazy / loader 등 data API도 함께 제공되고 있다. (공식 문서에서는 새롭게 추가된 라우터 중 하나로 업데이트 하는 것을 권장하고 있다. 추가적으로 data API는 현재 React Native에서는 지원하고 있지 않다.) 

  • createBrowserRouter - DOM History API를 사용하여 URL을 업데이트하고 히스토리 스택을 관리한다.
  • createMemoryRouter - 메모리 내에서 히스토리를 관리하며, 주로 테스트나 서버 측 렌더링에 사용된다.
  • createHashRouter - 해시(#)를 사용하여 페이지를 관리하며, 해시 기반 라우팅을 지원한다.
  • createStaticRouter - 정적 라우팅을 지원하며 서버 측 렌더링 및 정적 사이트 생성에 사용된다.

 


 

:D createBrowserRouter

 

createBrowserRouter는 DOM History API를 사용하여 URL을 업데이트하고 히스토리 스택을 관리한다. 라우트 객체는 children속성을 통해 중첩된 경로를 정의할 수 있다. (DOMRouterOpts의 자세한 설명은 공식문서를 참고하길 바란다.)

declare function createBrowserRouter(routes: RouteObject[], opts?: DOMRouterOpts): RemixRouter;

interface DOMRouterOpts {
    basename?: string;
    future?: Partial<Omit<RouterFutureConfig, "v7_prependBasename">>;
    hydrationData?: HydrationState;
    unstable_dataStrategy?: unstable_DataStrategyFunction;
    unstable_patchRoutesOnMiss?: unstable_PatchRoutesOnMissFunction;
    window?: Window;
}

 


 

:D RouteObject

 

라우트 객체는 아래와 같이 타입이 정의되어 있다. index 값에 따라 크게 IndexRouteObject 또는 NonIndexRouteObject로 구분할 수 있다. 

interface RouteObject {
  path?: string;
  index?: boolean;
  children?: RouteObject[];
  caseSensitive?: boolean;
  id?: string;
  loader?: LoaderFunction;
  action?: ActionFunction;
  element?: React.ReactNode | null;
  hydrateFallbackElement?: React.ReactNode | null;
  errorElement?: React.ReactNode | null;
  Component?: React.ComponentType | null;
  HydrateFallback?: React.ComponentType | null;
  ErrorBoundary?: React.ComponentType | null;
  handle?: RouteObject["handle"];
  shouldRevalidate?: ShouldRevalidateFunction;
  lazy?: LazyRouteFunction<RouteObject>;
}

 

 

각 속성에 대해 정리해보면 다음과 같다.

  • path - URL, 링크의 href, 또는 폼의 액션과 일치하는지 판단하기 위한 경로 패턴을 의미한다.
  • index - 해당 라우트가 인덱스 라우트인지 여부를 의미한다. (default : false)
  • children - 중첩 라우트를 사용할 경우, 하위 라우트를 의미한다.
  • caseSensitive : 경로가 정확히 일치해야 하는지 여부를 설정한다.(default : false)
  • loader - 경로에 등록된 컴포넌트가 렌더링되기 전에 호출되며, useLoaderData 훅을 통해 요소에 대한 데이터를 가져올 수 있다.
  • action - 경로에 폼(Form), fetcher, submission이 발생한 경우 호출된다.
아래와 같이 loader함수를 정의하여 사용할 수 있다. 나는 프로젝트에서 데이터가 로드되어야 하는 페이지에서는 로더함수를 따로 두어 구현하였다.(또한, tanstackQuery를 함께 사용하여 loader만을 사용했을 때 단점을 해결할 수 있었다. 이 부분은 다음 포스팅에서 다루려고 한다.)
export async function loader() { 
	// Response 객체 반환(이 외에도 다른 데이터도 타입도 반환 가능) 
	const response = await fetch('/api/data'); return response; 
}

이렇게 로더 함수를 정의하고 컴포넌트 내부에서 useLoaderData 훅을 사용하여 데이터를 가져올 수 있다. 
import { useLoaderData } from 'react-router-dom'; 

const initialData = useLoaderData();​
  • element / Component - 렌더링할 React 요소 / 컴포넌트를 의미한다.
  • errorElement / ErrorBoundary - 경로 렌더링 중 발생한 오류 상황(loader / action 등)에서 대신 렌더링된다. 
  • lazy - 애플리케이션 번들을 작게 유지하고 라우트 코드 스플릿을 지원하기 위해 사용한다. 

 

💡 위 action 속성에서 언급한 폼(Form) 컴포넌트는 우리가 생각하는 HTML의 form태그가 아니다. 이를 감싸고 있는 wrapper 컴포넌트 역할을 한다. 이 컴포넌트는 폼 제출 시 페이지를 새로고침하지 않고 처리하고 라우팅 할 수 있게 도와준다.(클라이언트 사이드에서 라우팅과 데이터 수정을 처리할 수 있게 해 준다.)

import { Form } from "react-router-dom";

function NewEvent() {
  return (
    <Form method="post" action="/events">
      <input type="text" name="title" />
      <input type="text" name="description" />
      <button type="submit">Create</button>
    </Form>
  );
}​​​

 
실제 구현 과정에서 Form 컴포넌트를 사용하며 이슈가 있었다. 공식문서를 꼼꼼히 읽어보지 않아서 발생한 문제였다. ㅠ.ㅠ

 

createBrowserRouter를 이용해 아래와 같은 라우터를 생성할 수 있다. 

import ErrorBoundary from '../ErrorBoundary';
import withSuspense from '@/shared/ui/withSuspense';
import { createBrowserRouter } from 'react-router-dom';
import RootLayout from '@/shared/ui/Layout/RootLayout';
import { QueryClient } from '@tanstack/react-query';

/** 애플리케이션 라우터 */
export const createRouter = (queryClient: QueryClient) => {
    const router = createBrowserRouter([
        {
            path: '/',
            element: <RootLayout />,
            errorElement: <ErrorBoundary />,
            children: [
                {
                    index: true /** 메인 페이지 */,
                    async lazy() {
                        const Main = await import('@/pages/main/index');
                        return { Component: withSuspense(Main.default) };
                    }
                },
                {
                    path: 'about/:userId' /** 사용자 상세 정보 */,
                    async lazy() {
                        const About = await import('@/pages/about/index');
                        return { Component: withSuspense(About.default), loader: About.loader(queryClient) };
                    }
                },
            ]
        }
    ]);

    return router;
};

 


 

:D Router Provider

 

모든 데이터 라우터 객체가 Router Provider에 전달되어 애플리케이션을 렌더링하고 data API를 활성화시키는 역할을 한다.
declare function RouterProvider({ fallbackElement, router, future, }: RouterProviderProps): React.ReactElement;
 Router Provider에 위에서 생성한 router객체를 아래와 같이 설정해 사용한다. 또한, createBroswerRouter는 마운트될 때, 경로에 일치하는 모든 로더를 실행한다. 이 때 fallbackElement 설정을 통해 로드 중임을 사용자에게 표시할 수 있다. 
ReactDOM.createRoot(document.getElementById("root")).render(
  <RouterProvider
    router={router}
    fallbackElement={<BigSpinner />}
  />
);

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

728x90

'REACT' 카테고리의 다른 글

big-calendar 라이브러리 사용하기 02  (1) 2024.11.28
big-calendar 라이브러리 사용하기 01  (2) 2024.10.28
Tanstack Router 사용하기 03  (0) 2024.08.04
Tanstack Router 사용하기 02  (0) 2024.07.25
Tanstack Router 사용하기 01  (6) 2024.07.24
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크