Search
Duplicate

[React] react-router를 사용한 라우팅과 window.location의 차이

간단소개
팔만코딩경 컨트리뷰터
ContributorNotionAccount
주제 / 분류
React
TypeScript
Scrap
태그
9 more properties
React에서 라우팅을 하려면 useNavigate 혹은 Navigation 컴포넌트를 활용한다. 기존에 vanila.js에서는 window.location을 통해서 이동하였는데 그 차이가 뭘까?
만약 Pick, Partial과 같은 Utility Type에 익숙하지 않다면 아래 포스팅을 먼저 읽고오자!

history.pushstate vs window.location

결론부터 말하자면 react-router의 useNavigatehistory.pushState를 사용한다.
history.pushstate는 HTTP 요청을 호출하지 않는다!
window.location.herf = “url”은 새 HTTP요청을 호출한다!
만약 url이 hash fragment이면 새 HTTP요청을 하지 않고 연관된 앵커로 스크롤된다.
history.pushstate
window.location.herf
HTTP 요청
 
페이지 새로고침
 
Application 상태 유지
 
위 특징을 보면 알 수 있듯이, 무조건 useNavigate를 사용할 필요는 없을 것 같다. 만약 이동과 동시에 새로고침이 필요하다면 window.locaiton.herf를 적절히 사용하면 되겠다.

react-router코드 살펴보기

그렇다면 react-router의 useNavigate가 어떻게 구현되어 있는지 살펴보자. 아래 코드는 useNavigate의 전체 코드이다.
코드가 너무 길어서 핵심만 보고싶다면 하이라이팅 된 부분만 살펴보자.
// context.ts import type { History, Location } from "history"; import { Action as NavigationType } from "history"; import type { RouteMatch } from "./router"; /** * A Navigator is a "location changer"; it's how you get to different locations. * * Every history instance conforms to the Navigator interface, but the * distinction is useful primarily when it comes to the low-level <Router> API * where both the location and a navigator must be provided separately in order * to avoid "tearing" that may occur in a suspense-enabled app if the action * and/or location were to be read directly from the history instance. */ export type Navigator = Pick<History, "go" | "push" | "replace" | "createHref">; interface NavigationContextObject { basename: string; navigator: Navigator; static: boolean; } export const NavigationContext = React.createContext<NavigationContextObject>( null! ); interface LocationContextObject { location: Location; navigationType: NavigationType; }
TypeScript
복사
// hooks.tsx import type { Location, Path, To } from "history"; import { LocationContext, NavigationContext, RouteContext } from "./context"; export interface NavigateFunction { (to: To, options?: NavigateOptions): void; (delta: number): void; } export interface NavigateOptions { replace?: boolean; state?: any; } export function useNavigate(): NavigateFunction { invariant( useInRouterContext(), // TODO: This error is probably because they somehow have 2 versions of the // router loaded. We can help them understand how to avoid that. `useNavigate() may be used only in the context of a <Router> component.` ); let { basename, navigator } = React.useContext(NavigationContext); let { matches } = React.useContext(RouteContext); let { pathname: locationPathname } = useLocation(); let routePathnamesJson = JSON.stringify( matches.map((match) => match.pathnameBase) ); let activeRef = React.useRef(false); React.useEffect(() => { activeRef.current = true; }); let navigate: NavigateFunction = React.useCallback( (to: To | number, options: NavigateOptions = {}) => { warning( activeRef.current, `You should call navigate() in a React.useEffect(), not when ` + `your component is first rendered.` ); if (!activeRef.current) return; if (typeof to === "number") { navigator.go(to); return; } let path = resolveTo( to, JSON.parse(routePathnamesJson), locationPathname ); if (basename !== "/") { path.pathname = joinPaths([basename, path.pathname]); } (!!options.replace ? navigator.replace : navigator.push)( path, options.state ); }, [basename, navigator, routePathnamesJson, locationPathname] ); return navigate; }
TypeScript
복사
결과적으로 useNavigation은 history api를 사용한다. 아래는 history 전체 소스코드이다.
그렇다면 의존하고 있는 history 모듈을 살펴보자. history api에서 살펴봐야할 것은 History, To와 같은 것들이다. useNavigate에서 라우팅을 해야할 때 사용하고 있는 push함수를 집중적으로 살펴 보자.
// index.ts export interface Path { pathname: Pathname; search: Search; hash: Hash; } export type To = string | Partial<Path>; export interface History { readonly action: Action; readonly location: Location; createHref(to: To): string; push(to: To, state?: any): void; replace(to: To, state?: any): void; go(delta: number): void; back(): void; forward(): void; listen(listener: Listener): () => void; block(blocker: Blocker): () => void; } let globalHistory = window.history; function push(to: To, state?: any) { let nextAction = Action.Push; let nextLocation = getNextLocation(to, state); function retry() { push(to, state); } if (allowTx(nextAction, nextLocation, retry)) { let [historyState, url] = getHistoryStateAndUrl(nextLocation, index + 1); // TODO: Support forced reloading // try...catch because iOS limits us to 100 pushState calls :/ try { globalHistory.pushState(historyState, "", url); } catch (error) { // They are going to lose state here, but there is no real // way to warn them about it since the page will refresh... window.location.assign(url); } applyTx(nextAction); } }
TypeScript
복사
위 함수를 살펴보면 결과적으로 useNavigate가 window.history.pushState를 사용한다는 것을 확인할 수 있다.

Reference

history.git
remix-run