오늘은 `react-router-dom` 라이브러리 없이 라우터를 만들어보면서 라우팅 시스템에 대해 공부해보려고 합니다.
라우팅 시스템 📖
라이브러리 없이 라우팅 시스템을 개발하기 위한 2가지 방법이 있습니다.
첫 번째 Fragment 해시(해시 라우터)를 이용하는 방법과
두 번째 history API(브라우저 라우터)를 이용하는 방법이 있습니다.
history API는 `pushState``popState`메서드가 지원되면서 Fragment 해시를 쓰지 않고도 라우팅 시스템을 개발할 수 있지만 모든 브라우저에서 지원되는 라우터를 개발하기 위해선 아직까지 Fragment 해시 라우터를 사용해야 합니다.
이 포스팅에선 Fragment 해시를 이용해 라우터 구현 방법에 대해 소개하겠습니다.
Fragment 해시(#)
Fragment 해시란 URL에 포함될 수 있는 해시로 시작하는 부분으로, 웹 페이지의 특정 섹션을 식별하는 것이 목적입니다.
예를 들어 `www.naver.com#/mingo`에서 `mingo`가 Fragment 해시이고 해시 변경이 일어났을 때 브라우저는 서버에 요청하지 않고 페이지가 새로고침 되지 않습니다.
또한 새로고침이나 리다이렉션이 발생했을 때, 해시 이전의 도메인 주소로 요청을 보내므로, 오류가 발생하지 않고 올바른 entry point로 요청합니다.
라우터 기능 📖
라우터를 만들기 위해 라우터의 기본적인 기능에 대해 먼저 알아보겠습니다.
- 애플리케이션의 경로 목록들을 저장 (URL을 DOM 구성 요소에 매핑)
- 현재 URL이 변경되면 페이지 콘텐츠를 해당 URL에 매핑된 요소로 변경
- URL에 path parameter나 query string을 식별
1. SPA 구축 📖
라우터가 잘 작동하는지 테스트하기 위해 간단한 SPA를 구축합니다.
<body>
<header>
<a href="#/">home</a>
<a href="#/mingo">melon</a>
</header>
<main></main>
<script type="module" src="index.js"></script>
</body>
`home`과 `mingo`페이지를 보여줄 간단한 text도 준비합니다.
// index.js
const container = document.querySelector("main")
const pages = {
home: () => container.innerText = '메인 페이지',
mingo: () => container.innerText = '밍고 페이지',
}
2. 라우터 구현 📖
Fragment 해시랑 pages 함수를 연결하는 라우터 기능을 구현합니다.
// router.js
export default function createRouter() {
const routes = [];
const router = {
addRoute(fragment, component) {
routes.push({fragment, component});
return this;
},
start() {
const checkRoutes = () => {
const currentRoute = routes.find(route => route.fragment === window.location.hash);
currentRoute.component();
}
window.addEventListener('hashchange', checkRoutes);
checkRoutes();
}
};
return router;
}
routes
애플리케이션 경로 목록을 저장하고, URL을 구성 요성에 매핑한 객체들을 저장하는 배열입니다.
const routes = [];
router
라우터를 구현한 객체입니다.
const router = {
...
}
addRoute
routes 배열에 URL과 구성 요소들을 매핑해 저장하기 위한 메서드입니다.
addRoute(fragment, component) {
routes.push({fragment, component});
return this;
},
start
URL 변화를 감지하는 메서드입니다.
start() {
const checkRoutes = () => {
const currentRoute = routes.find(route => route.fragment === window.location.hash);
currentRoute.component();
}
window.addEventListener('hashchange', checkRoutes);
checkRoutes();
}
router를 SPA에 연결
// index.js
import createRouter from "./router.js";
const container = document.querySelector("main")
const pages = {
home: () => container.innerText = "메인 페이지",
mingo: () => container.innerText = "밍고 페이지",
}
const router = createRouter();
router.addRoute("#/", pages.home)
.addRoute("#/mingo", pages.mingo)
.start();
3. navigate 📖
기존 `<a>`태그 없이 페이지 이동이 필요한 경우에도 라우터가 이를 감지해 Fragment 해시값을 바꿔줘야 합니다. `<a>`태그를 `<button>`태그로 바꾸고 데이터 속성을 이용해 경로를 추가해 줍니다.
<body>
<header>
<button data-navigate="/">home</button>
<button data-navigate="/mingo">mingo</button>
</header>
<main></main>
<script type="module" src="index.js"></script>
</body>
버튼을 클릭했을 때, 클릭된 버튼의 데이터 속성에 있는 경로들을 라우터에 전달해 주는 이벤트리스터를 추가하고, 전달받은 Fragment 해시로 URL 해시값을 바꿔주는 `navigate`메서드를 추가합니다.
// index.js
...
window.addEventListener("click", event => {
if(event.target.matches("[data-navigate]")) {
router.navigate(event.target.dataset.navigate);
}
});
// router.js
const router = {
...
navigate(fragment) {
window.location.hash = fragment;
}
}
location replace
navigate 메서드의 `location replace`옵션도 간단하게 추가할 수 있습니다.
// router.js
const router = {
...
navigate(fragment, replace = false) {
if (replace) {
const href = window.location.href.replace(window.location.hash, "#" + fragment);
window.location.replace(href);
} else {
window.location.hash = fragment;
}
}
}
현재 URL의 해시 부분을 클릭한 fragment(해시)로 변경 후 현재 브라우저의 URL을 href로 변경 (이때 브라우저 히스토리에 기존 URL이 기록되지 않음. 뒤로 가기 X)
const href = window.location.href.replace(window.location.hash, "#" + fragment);
window.location.replace(href);
회고 🧐
라우터를 직접 구현해 보면서 라우팅 시스템에 대해 이해를 할 수 있어서 좋았습니다.
출처 🏷️
https://fe-developers.kakaoent.com/2022/221124-router-without-library/
'공부 > react' 카테고리의 다른 글
useSyncExternalStore 훅 알아보기 (1) | 2024.08.28 |
---|---|
React는 Hooks를 배열로 관리하는 이유 (0) | 2024.08.19 |
React 숙련 4 (react-router-dom) (0) | 2024.08.16 |
React 숙련 3 (Memoization) (0) | 2024.08.16 |
React 숙련 2 (react hooks) (1) | 2024.08.16 |
React 숙련 1 (styled-components) (0) | 2024.08.16 |
React 입문 6 (DOM, VirtualDOM) (0) | 2024.08.09 |
React 입문 5 (명령형, 선언형) (0) | 2024.08.09 |