History API 를 이용한 Router 구현하기
[클릭]
이전에는 History API 를 통해서 SPA Router 를 구현해 봤다.
window.history.pushState() 함수를 사용하여 새로운 브라우저 히스토리 항목을 추가하고, 이를 통해 URL을 변경하는 방식으로 페이지 이동을 처리하고 있습니다.
또한, 'popstate' 이벤트를 감지하고 이 이벤트를 통해 URL이 변경되면 적절한 컴포넌트를 렌더링하는 로직을 구현하고 있습니다. 이는 페이지를 새로 고칠 필요 없이 사용자에게 새로운 페이지를 보여줄 수 있게 해줍니다.
이렇게 구현하면 전통적인 다중 페이지 방식보다 더 빠르고 매끄러운 사용자 경험을 제공이 가능하다!
이번에는 React useRouter Hook 을 만들어 보려고 한다.
이번 Wanted onBoarding 을 하면서 다른사람의 코드를 바라보며 내것으로 만드는것도 중요하다고 생각이 들기에 참고를 해서 공부를 했습니다.
참고 [https://github.com/hayhyang/react-anatomy] 양식을 주셔서 정말 감사합니다. (https://github.com/hayhyang/react-anatomy)
main.tsx => App.tsx
'Router' , 'Routes' , 'Route' 와 같이 라우팅 관련 컴포넌트는 각각 다른 역할을 수행하게 됩니다.
import { Dispatch, ReactNode, SetStateAction, createContext, useMemo, useState } from "react";
interface RouterProps {
children: ReactNode;
}
export const RouterContext = createContext({
path: window.location.pathname,
setPath: (() => null) as Dispatch<SetStateAction<string>>,
});
const Router = ({ children }: RouterProps) => {
const [path, setPath] = useState(window.location.pathname);
const value = useMemo(() => ({ path, setPath }), [path, setPath]);
return <RouterContext.Provider value={value}>{children}</RouterContext.Provider>;
};
export default Router;
import { ReactNode, useContext, ReactElement } from "react";
import { RouterContext } from "./Router";
import React from "react";
// RoutesProps : Routes props 에 대한 타입을 정의
interface RoutesProps {
children: ReactNode | ReactNode[];
}
const Routes = ({ children }: RoutesProps) => {
const { path, setPath } = useContext(RouterContext);
window.onpopstate = () => setPath(window.location.pathname);
let matchedComponent = null;
//이 함수는 children props 에서 각 child 를 순회하며 , 각 child가 유효한 React
// 요소인지 확인(React.isValidElement 하고 , 현재 경로 path 와 일치하는지 확인
// 일치하는 경우 , 해당 child component prop을 mathedComponent 변수에 할당합니다.
React.Children.forEach(children, (child) => {
if (React.isValidElement(child) && path === (child as ReactElement).props.path) {
console.log(
"current",
(child as ReactElement).props.path,
(child as ReactElement).props.component
);
matchedComponent = (child as ReactElement).props.component;
}
});
return matchedComponent;
};
export default Routes;
import { ReactNode } from "react";
interface RouteProps {
path: string;
component: ReactNode;
}
const Route = (props: RouteProps) => null;
export default Route;
Route 컴포넌트는 실제로 아무것도 렌더링 하지 않습니다. 이 컴포넌트의 주 목적은 , 라우팅 로직에 필요한 path 와 component props 를 제공하는 것 입니다.
'Routes' 컴포넌트는 'Route' 컴포넌트의 props 를 사용하여 경로와 컴포넌트를 매핑하고 , 현재 경로에 해당하는 컴포넌트 렌더링을 합니다 .따라서 , 라우팅 로직에 대한 정보를 제공하는 역할을 하고 있습니다.
Route 에 제공된 path , component 에 매핑을 하고
Hook 폴더에 useRouter.tsx
const useRouter = () => {
const push = (path: string) => {
history.pushState(null, "", path);
const popStateEvent = new PopStateEvent("popstate");
dispatchEvent(popStateEvent);
};
return { push };
};
export default useRouter;
useRouter 커스텀 훅은 웹페이지의 현재 URL 을 바꾸는 기능을 제공합니다.
상수 push 함수를 통해서 path 라는 문자열을 인수를 받아서 , 그 값을 새로운 URL 로 사용합니다. 이 함수는 이전에 History API 를 사용했던 history.pushState() 를 사용하여 , 첫 번째 인수로는 상태 객체를 받고 , 두번째 인수로는 제목을 받지만 , 이 예제에서는 둘다 사용하지 않고 세 번째인 path 만 사용하여 새로운 url 을 받습니다.
new PopStateEvent("popstate") 와 dispatchEvent(popStateEvent) 이 두줄은 브라우저에 "popstate" 라는 이벤트가 발생했다는 신호를 내보냅니다. 이 이벤트는 브라우저의 히스토리 엔트리가 변경될 때 발생하는 이벤트입니다.
useRouter 훅을 완성 시켰으므로 , push 함수를 반환하고 이 훅을 사용하는 컴포넌트는 이 함수를 호출하여 페이지를 이동시킬수 있습니다.
import reactLogo from "../assets/react.svg";
import viteLogo from "/vite.svg";
import "../App.css";
import useRouter from "../hook/useRouter";
const Main = () => {
const { push } = useRouter();
const ClickFly = () => {
push("/about");
};
return (
<>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React Main Home</h1>
<button onClick={ClickFly}>About Page </button>
<p>Root</p>
<p className="read-the-docs">Click on the Vite and React logos to learn more</p>
</>
);
};
export default Main;
구조분해 할당을 통해서
const {push} = useRouter(); 를 상수로 선언후 클릭함수를 만들어서 구현을 완성 시켰습니다