useNavigate
를 중심으로 React-Router의 내부 코드 살펴보기
React-Router는 SPA(Single Page Application)를 멀티 페이지처럼 느껴지도록 만드는
라우팅 라이브러리입니다. 이번 글에서는 React-Router의 방대한 코드 중 페이지 이동에
사용되는 useNavigate
Hook과 관련된 내부 동작을 중심으로 실제 구현을
살펴보겠습니다.
라우터 설정과 RouterProvider
React-Router의 기본 사용법은 routes
를 정의한 뒤 createBrowserRouter
와
RouterProvider
를 이용해 라우팅 기능을 프로젝트에 추가하는 것입니다.
const routes: RouteObject[] = [
{
path: MAIN,
element: <TodoPage />,
},
];
const router = createBrowserRouter(routes);
export function AppRouter() {
return <RouterProvider router={router} />;
}
위 코드는 다음과 같은 과정으로 동작합니다:
-
createBrowserRouter
는 라우팅 객체를 생성합니다. -
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();
}
이 함수는 다음 작업을 수행합니다:
- 라우터 객체 생성:
createRouter
를 호출해 라우터 객체를 만듭니다. - 초기화 수행: 생성된 라우터 객체의
initialize
메서드를 실행합니다.
createRouter
와 initialize
메서드
initialize
메서드는 라우터 초기화 과정에서 핵심적인 역할을 담당합니다. 주요 기능은 다음과 같습니다:
-
이벤트 리스너 등록:
- 브라우저의
popstate
이벤트를 감지하여 URL 변경 시 라우터 상태를 업데이트합니다. - React-Router는 이를 통해 브라우저 뒤로 가기/앞으로 가기 버튼에 대한 동작을 처리합니다.
- 브라우저의
-
초기 페이지 렌더링:
- 초기 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의 주요 기능
popstate
이벤트 처리: 브라우저의 뒤로가기/앞으로가기를 통한 URL 변경 이벤트를 감지해 상태를 업데이트합니다.- 라우트 매칭: URL과 매칭되는 라우트를 찾아 해당 컴포넌트를 렌더링합니다.
-
navigate
메서드:window.history.pushState
를 통해 URL 변경dispatchEvent
를 통해 popstate 이벤트 생성
결론
React-Router는 내부적으로 에러 핸들링이나 최적화를 포함한 다양한 로직을 포함하고 있으나 아주 기본이 되는 원리는 아래와 같습니다:
listner
를 통해 브라우저의 뒤로가기/앞으로가기 대응window.history.pushState
를 통해 브라우저 히스토리에 URL 추가URL
에 일치하는 경로를 가진 route 렌더링
React-Router에 대해 실제 코드를 뜯어봤을 때는 정말 어렵고 난해했는데요, 가장 기본이 되는 원리만을 놓고 봤을 때는 생각보다 쉽게
느껴지기도 했습니다.
제가 작성한 내용이나 코드 관련해서 잘못된 부분이나 궁금한 부분이 있다면 댓글 부탁드립니다!
'React' 카테고리의 다른 글
useCallback 알아보기 (2) | 2024.10.14 |
---|---|
useRef 알아보기 (0) | 2024.10.13 |
useState 깊게 분석하기 (6) | 2024.10.10 |