React Router DOM을 Suspense와 같이 작성해야 하는 이유

React Router DOM을 Suspense와 같이 작성해야 하는 이유

React 프로젝트에서 React Router DOM으로 라우팅을 관리할 때 Suspense, lazy까지 활용해야 하는 이유 및 과정을 작성했습니다.

·

2 min read

React Router DOM

React 프로젝트에서는 React Router DOM으로 페이지 라우팅을 관리할 수 있다.

공식문서에서는 Data API를 활용할 수 있는 createBrowserRouter를 통해 라우팅을 구성하는 예제를 보여준다.

  • RouteObject마다 렌더링할 컴포넌트를 명시할 수 있다.

    • 예제 프론트엔드에는 /home, /detail, /settings 주소가 존재한다.

    • 각 주소마다 Home, Detail, Settings 페이지 컴포넌트가 작성되어있다.

  • 하나의 라우팅에 여러 개의 하위 라우팅을 children 형태로 적용할 수 있다.

  • Home, Children 컴포넌트 모두 Layout 컴포넌트 내부에 배치될 수 있다.

const routes: RouteObject[] = [
  {
    path: "/",
    children: [
      {
        path: "/",
        element: <Layout />,
        children: [
          {
            path: "/home",
            element: <Home />,
          },
          {
            path: "/detail",
            element: <Detail />,
          },
        ],
      },
      {
        path: "/settings",
        element: <Setting />,
      },
    ],
  },
];

export default routes;

이슈

개발 도중 네트워크 요청이 예상보다 많이 발생한다는 이슈가 있어 확인 결과 Home 컴포넌트에서 Detail 컴포넌트 또한 렌더링되는 것을 확인할 수 있었다.

Detail 컴포넌트가 렌더링되면 컴포넌트 내부에 작성된 Hooks가 호출되면서 관련 네트워크 요청이 실행되는 것이었다.

각 라우팅에 걸려있는 컴포넌트마다 Suspense 컴포넌트를 적용해서 특정 페이지에 접속하면 해당하는 컴포넌트만 실행되도록 리팩토링 작업을 거쳤다.

우선 페이지 컴포넌트를 export default로 모듈을 내보낼 수 있어야 한다.

  • lazy에는 컴포넌트를 동적으로 불러오는 함수를 인자로 전달한다.

  • 동적 Import로 반환되는 컴포넌트는 Promise 형태이다.

  • 프론트엔드가 컴포넌트를 렌더링할 때 Promise가 완료될 때까지 대기한 다음 default 속성의 값을 리액트 컴포넌트로 렌더링하기 때문이다.

// export function Home()
export default function Home() {
  const location = useLocation();
  const { data, error, isLoading } = useHomeData();

  return (
    <div>
      <h1>Home page</h1>
    </div>
  );
}

각 컴포넌트를 React.lazy로 모듈을 불러오게 변경한다.

  • lazy는 컴포넌트가 첫번째로 렌더링될 때까지 컴포넌트 실행을 연기시킨다.

  • 각 페이지 컴포넌트를 Suspense의 하위 컴포넌트 형태로 작성한다.

    • Suspense는 컴포넌트가 렌더링 완료될 때까지 fallback으로 지정된 컴포넌트를 보여줄 수 있다.
const Home = lazy(() => import("./pages/Home"));

const routes: RouteObject[] = [
  {
    path: "/",
    children: [
      {
        path: "/",
        element: <Layout />,
        children: [
          {
            path: "/home",
            element: (
              <Suspense>
                <Home />
              </Suspense>
            ),
          },
          {
            path: "/detail",
            element: (
              <Suspense>
                <Detail />
              </Suspense>
            ),
          },
        ],
      },
      {
        path: "/settings",
        element: (
          <Suspense>
            <Setting />
          </Suspense>
        ),
      },
    ],
  },
];

export default routes;

결과

페이지를 이동할 때마다 각 페이지에 해당하는 컴포넌트가 필요한 네트워크 요청만 실행하기 때문에 네트워크 요청 수를 절약할 수 있었다.

추가적인 컴포넌트를 작성해야 하는 것도 알 수 있었다.

  • lazy 함수 호출하는 과정에서 프로미스가 실패할 것을 대비해서 ErrorBoundary 역할의 컴포넌트

  • lazy 함수 호출 후 컴포넌트를 렌더링하는 동안 대신 렌더링할 fallback 역할의 컴포넌트