React 프로젝트에서 주로 React-router를 활용하여 SPA(Single Page Application)을 구현합니다. 하지만 SPA도 브라우저의 히스토리가 사용됩니다. 이를 간단하게 웹 API를 사용하여 약식으로 구현해보고자 합니다.
완성된 프로젝트의 소스코드 링크는 아래와 같습니다.
https://github.com/suhong99/spa_simple_router
GitHub - suhong99/spa_simple_router: React와 History API 사용하여 SPA Router 기능 구현
React와 History API 사용하여 SPA Router 기능 구현. Contribute to suhong99/spa_simple_router development by creating an account on GitHub.
github.com
구현할 내용은 about과 main 페이지를 직접 구현한 라우터를 위해여서 이동하는 것이고 이때 브라우저의 뒤로가기 버튼을 통해서 전 페이지로 이동할 수 있어야 합니다.
구현하기
우선 SPA에 사용할 브라우저 API를 먼저 언급하려고 합니다.
History API 를 통해서 실제 페이지를 이동했던 것 처럼 방문했던 페이지들을 기록해야 합니다.
History 객체란
- 브라우저가 관리하는 객체로, 사용자가 방문한 페이지의 URL 정보를 담고 있습니다.
- pushState 메서드를 호출하면 브라우저의 히스토리 스택에 새로운 기록이 추가됩니다.
이후 우리가 브라우저 상단의 뒤로가기 버튼 클릭시에 windown의 popState 이벤트가 발생하게 됩니다. 이 경우에
히스토리 스택에서 이전 URL을 참고하게 됩니다.
이제 실제 구현해보려고 합니다.
Router
흔히 React-Router 라이브러리를 사용하게 되면 App에 browswer라우터를 등록하게 됩니다. 따라서 저희는 우선 Router를 먼저 구현해보려고 합니다.
import React, {
Children,
useEffect,
useState,
ReactNode,
isValidElement,
} from 'react';
interface RouterProps {
children: ReactNode;
}
const Router: React.FC<RouterProps> = ({ children }) => {
const [currentPath, setCurrentPath] = useState(window.location.pathname);
useEffect(() => {
const onPopState = () => setCurrentPath(window.location.pathname);
window.addEventListener('popstate', onPopState);
return () => window.removeEventListener('popstate', onPopState);
}, []);
return (
<>
{Children.map(children, (child) => {
if (isValidElement(child) && child.props.path === currentPath) {
return child;
}
return null;
})}
</>
);
};
export default Router;
우리는 url의 경로가 바뀌게 되면 보여주는 컴퍼넌트가 달라집니다. 이때 달라지는 컴퍼넌트가 페이지 단위가 됨녀 SPA가 완성이 되는 것입니다.
.따라서 App을 Router로 감싼 이후에 내부에 있는 Route를 통해서 각각의 경로와 경로에 일치하는 컴퍼넌트를 감지할 것 입니다.
이때 뒤로가기가 작동하게 하기 위해서 useEffect를 통해서 사이드 이펙트를 감지할 것이고, 발생할 때마다 현재 경로를useState로 선택한 지역상태를 통해 관리할 것 입니다.
그리고 이를 통해서 컴퍼넌트가 렌더링 가능한 컴퍼넌트이고, 경로가 일치하면 반환하는 식으로 구현하였습니다.
{Children.map(children, (child) => {
if (isValidElement(child) && child.props.path === currentPath) {
return child;
}
return null;
유의사항 : 이때 map은 js의 map 이 아닌 리엑트 Children의 map입니다.
Route
이제 하위에서 경로와 렌더링할 컴퍼넌틀틀 받는 Route 컴퍼넌트를 작성할 것 입니다.
import React, { ReactElement } from 'react';
interface RouteProps {
path: string;
component: ReactElement;
}
const Route: React.FC<RouteProps> = ({ component }) => component;
export default Route;
컴퍼넌트와 props를 등록하여서 Router에서 사용할 수 있도록 만들어줍니다.
useRouter 훅
import { useCallback } from 'react';
const useRouter = () => {
const push = useCallback((path: string) => {
window.history.pushState({}, '', path);
const popStateEvent = new PopStateEvent('popstate');
window.dispatchEvent(popStateEvent);
}, []);
return { push };
};
export default useRouter;
이후 페이지 이동시에는 pushState함수를 이용해서 History API에 방문하는 페이지를 기록해줍니다.
해당 훅의 사용방법은 아래와 같습니다.
import React from 'react';
import useRouter from '../router/hook/useRouter';
interface NaviButtonProps {
text: string;
url: string;
}
const NaviButton: React.FC<NaviButtonProps> = ({ text, url }) => {
const { push } = useRouter();
const handleClick = () => {
push(url);
};
return (
<button
onClick={handleClick}
style={{
backgroundColor: 'rgba(0, 0, 0, 0.7)',
color: 'white',
border: 'none',
borderRadius: '8px',
padding: '10px 20px',
cursor: 'pointer',
}}
>
{text}
</button>
);
};
export default NaviButton;
참고문헌
https://developer.mozilla.org/ko/docs/Web/API/Window/popstate_event
popstate - Web API | MDN
Window 인터페이스의 popstate 이벤트는 사용자의 세션 기록 탐색으로 인해 현재 활성화된 기록 항목이 바뀔 때 발생합니다. 만약 활성화된 엔트리가 history.pushState() 메서드나 history.replaceState() 메서
developer.mozilla.org
https://ko.react.dev/reference/react/isValidElement
isValidElement – React
The library for web and native user interfaces
ko.react.dev
https://developer.mozilla.org/ko/docs/Web/API/History_API
History API - Web API | MDN
History API는 history 전역 객체를 통해 브라우저 세션 히스토리(웹 익스텐션 히스토리와 혼동해서는 안 됩니다.)에 대한 접근을 제공합니다. 사용자의 방문 기록을 앞뒤로 탐색하고, 방문 기록 스택
developer.mozilla.org
'프론트엔드 > React' 카테고리의 다른 글
WebRTC 기술 구현하기 (0) | 2024.12.11 |
---|---|
URL 기반 검색 기능 만들기 (URLSearchParams, Router) (1) | 2024.10.26 |
Intersection Observer API를 활용하여 무한 스크롤 구현하기 (0) | 2024.10.07 |
input 요소 uncontrolled와 controlled 관련 Warning (0) | 2024.05.01 |
React에서 불변성을 지켜야 하는 이유 (0) | 2024.04.11 |