본문 바로가기
React

React-Router 기본 원리 알아보기

by East-K 2024. 11. 18.

useNavigate를 중심으로 React-Router의 내부 코드 살펴보기

React-Router는 SPA(Single Page Application)를 멀티 페이지처럼 느껴지도록 만드는 라우팅 라이브러리입니다. 이번 글에서는 React-Router의 방대한 코드 중 페이지 이동에 사용되는 useNavigate Hook과 관련된 내부 동작을 중심으로 실제 구현을 살펴보겠습니다.

라우터 설정과 RouterProvider

React-Router의 기본 사용법은 routes를 정의한 뒤 createBrowserRouterRouterProvider를 이용해 라우팅 기능을 프로젝트에 추가하는 것입니다.


const routes: RouteObject[] = [
  {
    path: MAIN,
    element: <TodoPage />,
  },
];

const router = createBrowserRouter(routes);

export function AppRouter() {
  return <RouterProvider router={router} />;
}
  

위 코드는 다음과 같은 과정으로 동작합니다:

  1. createBrowserRouter는 라우팅 객체를 생성합니다.
  2. RouterProvider는 이 라우터 객체를 기반으로 URL과 컴포넌트를 매핑하여 화면에 렌더링합니다.

RouterProvider의 내부 구조

RouterProvider는 라우팅을 관리하기 위해 다양한 컨텍스트와 컴포넌트를 활용합니다. 주요 요소는 다음과 같습니다:

  • DataRouterContext: useNavigate 등에서 사용하는 컨텍스트입니다.
  • Router: URL 및 상태와 관련된 정보를 관리하며, NavigationContext에 값을 등록합니다.
  • MemoizedDataRoutes: 내부적으로 useRoutesImpl를 통해 현재 URL과 매칭되는 라우트 컴포넌트를 화면에 렌더링하는 역할을 합니다.

// packages/router/router.ts -> RouterProvider의 반환값
<>
  <DataRouterContext.Provider value={dataRouterContext}>
    <DataRouterStateContext.Provider value={state}>
      <FetchersContext.Provider value={fetcherData.current}>
        <ViewTransitionContext.Provider value={vtContext}>
          <Router
            basename={basename}
            location={state.location}
            navigationType={state.historyAction}
            navigator={navigator}
            future={routerFuture}
          >
            {state.initialized || router.future.v7_partialHydration ? (
              <MemoizedDataRoutes
                routes={router.routes}
                future={router.future}
                state={state}
              />
            ) : (
              fallbackElement
            )}
          </Router>
        </ViewTransitionContext.Provider>
      </FetchersContext.Provider>
    </DataRouterStateContext.Provider>
  </DataRouterContext.Provider>
  {null}
</>
  

createBrowserRouter의 역할

createBrowserRouter는 React-Router의 라우터 객체를 생성하는 함수입니다. 내부적으로 createRouter를 호출하며, 이를 초기화(initialize)합니다.


// packages/react-router-dom/index.tsx (일부 생략)
export function createBrowserRouter(
  routes: RouteObject[],
  opts?: DOMRouterOpts
): RemixRouter {
  return createRouter({
    history: createBrowserHistory(),
    routes,
    future: {
      v7_prependBasename: true,
    },
  }).initialize();
}
  

이 함수는 다음 작업을 수행합니다:

  1. 라우터 객체 생성: createRouter를 호출해 라우터 객체를 만듭니다.
  2. 초기화 수행: 생성된 라우터 객체의 initialize 메서드를 실행합니다.

createRouterinitialize 메서드

initialize 메서드는 라우터 초기화 과정에서 핵심적인 역할을 담당합니다. 주요 기능은 다음과 같습니다:

  1. 이벤트 리스너 등록:
    • 브라우저의 popstate 이벤트를 감지하여 URL 변경 시 라우터 상태를 업데이트합니다.
    • React-Router는 이를 통해 브라우저 뒤로 가기/앞으로 가기 버튼에 대한 동작을 처리합니다.
  2. 초기 페이지 렌더링:
    • 초기 URL을 기준으로 라우트 객체를 검색해, 일치하는 컴포넌트를 렌더링합니다.

// packages/router/router.ts -> createRouter 함수의 내부 함수
function initialize() {
  // listener 등록
  unlistenHistory = init.history.listen(
    // historyAction: 'PUSH'
    ({ action: historyAction, location, delta }) => {
      return startNavigation(historyAction, location);
    }
  );

  // 초기 페이지 렌더링
  if (!state.initialized) {
    // HistoryAction.Pop: 'POP'
    startNavigation(HistoryAction.Pop, state.location, {
      initialHydration: true,
    });
  }

  return router;
}
  

CustomRouter로 구현

위에서 설명한 동작을 최소한의 코드로 구현하면 다음과 같습니다:


export const CustomRouterProvider = ({ router }: { router: RouteObject[] }) => {
  const [currentPath, setCurrentPath] = useState(() => extractPath());

  // popstate 이벤트 처리
  const handlePop = () => setCurrentPath(extractPath());
  useEffect(() => {
    window.addEventListener("popstate", handlePop);
    return () => {
      window.removeEventListener("popstate", handlePop);
    };
  }, []);

  // navigate 함수
  const navigate = useCallback((path: string) => {
    window.history.pushState({ path }, "", path);
    window.dispatchEvent(new PopStateEvent("popstate"));
  }, []);

  // 현재 경로와 매칭되는 컴포넌트 찾기
  const matchedRouteElement = useMemo(() => {
    return router.find((route) => route.path === currentPath)?.element ?? null;
  }, [currentPath, router]);

  return (
    
      {matchedRouteElement}
    
  );
};
  

CustomRouter의 주요 기능

  1. popstate 이벤트 처리: 브라우저의 뒤로가기/앞으로가기를 통한 URL 변경 이벤트를 감지해 상태를 업데이트합니다.
  2. 라우트 매칭: URL과 매칭되는 라우트를 찾아 해당 컴포넌트를 렌더링합니다.
  3. navigate 메서드:
    • window.history.pushState를 통해 URL 변경
    • dispatchEvent를 통해 popstate 이벤트 생성

결론

React-Router는 내부적으로 에러 핸들링이나 최적화를 포함한 다양한 로직을 포함하고 있으나 아주 기본이 되는 원리는 아래와 같습니다:

  1. listner를 통해 브라우저의 뒤로가기/앞으로가기 대응
  2. window.history.pushState를 통해 브라우저 히스토리에 URL 추가
  3. URL에 일치하는 경로를 가진 route 렌더링

React-Router에 대해 실제 코드를 뜯어봤을 때는 정말 어렵고 난해했는데요, 가장 기본이 되는 원리만을 놓고 봤을 때는 생각보다 쉽게 느껴지기도 했습니다.
제가 작성한 내용이나 코드 관련해서 잘못된 부분이나 궁금한 부분이 있다면 댓글 부탁드립니다!

'React' 카테고리의 다른 글

useCallback 알아보기  (2) 2024.10.14
useRef 알아보기  (0) 2024.10.13
useState 깊게 분석하기  (6) 2024.10.10