블로그에서 보기 => https://jerrynim.dev/post/vanilla-route
바닐라 자바스크립트에서 라우팅을 만드는 작업을 함께 보도록 하겠습니다. 이 블로그에서도 사용하고 있는 방식으로 브라우저의 history API, popstate 이벤트, 커스텀 이벤트를 사용하여 제작하게 됩니다.
프로젝트 설정
우리에게 필요한 것은 html과 js 파일이 전부입니다.
기본적인 html 파일을 만들도록 하겠습니다.
index.html
<!DOCTYPE html>
<html>
<body>
<main></main>
</body>
</html>
실행될 js 파일을 만들어 추가하도록 하겠습니다.
index.html 파일을 열 때 내부 콘텐츠를 설정하도록 하겠습니다.
index.js
window.onload = () => {
const main = document.querySelector("main");
main.innerHTML = "<div>js loaded</div>";
};
만들어준 js 파일을 사용하도록 index.html에서
<!DOCTYPE html>
<html>
<body>
<script src="./index.js"></a>
<main></main>
</body>
</html>
index.js
window.onload = () => {
const main = document.querySelector("main");
main.innerHTML = "<div>js loaded</div>";
};
<a>
태그와 window.location.href 값을 변경하는 방법을 통해 이동을 할 수 있지만, 페이지 자체를 새로 불러오기 때문에 같은 어플리케이션이라고 해도 깜박임이 발생하게 됩니다.
주소를 이동해도 페이지 자체가 새로고침이 되지 않기 위해서
https://developer.mozilla.org/en-US/docs/Web/API/Window/history
window.history의 pushState 메서드를 사용하여 주소 변경을 할 수 있습니다.
pushState 메서드는 (데이터, 타이틀, URL)의 매개변수를 받도록 되어있습니다.
window.history.pushState(undefined,"타이틀","/some")
메서드를 실행시켜보면 주소창의 주소가 변경된 것을 확인할 수 있습니다.
주소가 바뀌었다면 이를 감지하여 <main>
태그내의 콘텐츠를 바꾸는것이 라우팅의 원리가 되겠습니다.
주소가 변경된 것을 알리고 감지하기 위하여 locationchange라는
https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent
커스텀 이벤트를 만들도록 하겠습니다.
index.js
window.onload = () => {
const main = document.querySelector("main");
const handleLocationChange = (e) => {
console.log("locationchanged");
};
window.addEventListener("locationchange", handleLocationChange);
main.innerHTML = "<div>js loaded</div>";
};
주소 변경 이벤트를 발생시키는 버튼을 만들어 주소 변경 및 이벤트 감지를 확인해 보도록 하겠습니다.
index.js
window.onload = () => {
const main = document.querySelector("main");
const handleLocationChange = (e) => {
console.log("locationchanged");
//* 주소변경
window.history.pushState(undefined, "타이틀", "/some");
};
//* locationchange 이벤트리스너
window.addEventListener("locationchange", handleLocationChange);
main.innerHTML = "<div><button type='button'>move to /some</button></div>";
const button = document.querySelector("button");
button.addEventListener("click", () => {
const locationChangeEvent = new CustomEvent("locationchange", {
composed: true, //웹 컴포넌트라면 넣어주세요
});
//* 주소변경 이벤트 Dispatch
window.dispatchEvent(locationChangeEvent);
});
};
버튼을 클릭하면 콘솔 창에 메세지가 출력되고 브라우저의 주소가 변경되는 것을 확인할 수 있습니다.
locationchange 이벤트가 항상 같은 주소로 이동하는 것은 아니기에 변경할 주소를 인자로 받을 수 있도록 하도록 하겠습니다.
커스텀 이벤트를 생성할 때에 detail에 값을 주어 원하는 변수를 전달할 수 있습니다.
index.js
window.onload = () => {
const main = document.querySelector("main");
const handleLocationChange = (e) => {
const { href } = e.detail;
console.log(href);
//* 주소변경
window.history.pushState(undefined, "타이틀", href);
};
//* locationchange 이벤트리스너
window.addEventListener("locationchange", handleLocationChange);
main.innerHTML = "<div><button type='button'>move to /some</button></div>";
const button = document.querySelector("button");
button.addEventListener("click", () => {
const locationChangeEvent = new CustomEvent("locationchange", {
composed: true,
detail: { href: "some" },
});
//* 주소변경 이벤트 Dispatch
window.dispatchEvent(locationChangeEvent);
});
};
주소이동에 성공했으니 주소에 맞게 콘텐츠를 변경하도록 하면 라우팅을 구현할 수 있습니다. 이때 window.location.pathname을 사용하여 라우팅 경로를 받아오도록 합니다. pathname을 사용하는 이유는 "/some? page=1"처럼 쿼리 값이 있을 때 쿼리 값을 제외한 경로를 받을 수 있기 때문입니다.
index.js
//* 경로에 맞는 콘텐츠 렌더
const renderContents = () => {
const { pathname } = window.location;
switch (pathname) {
case "/some":
main.innerHTML = "<div>some Contents</div>";
break;
default:
main.innerHTML = "<div>404</div>";
}
};
const handleLocationChange = (e) => {
const { href } = e.detail;
//* 주소변경
window.history.pushState(undefined, "타이틀", href);
//* 콘텐츠 렌더링
renderContents();
};
locationchange 커스텀 이벤트를 통해 라우팅을 하는데 성공했지만, 뒤로 가기 혹은 앞으로 가기를 할 때 주소는 이동하지만 콘텐츠가 바뀌지 않는 것을 볼 수 있습니다.
이를 위해 뒤로 가기 및 앞으로가기 이벤트를 감지하는
https://developer.mozilla.org/ko/docs/Web/API/Window/popstate_event
popstate 이벤트를 이용하여 변경된 주소로 콘텐츠를 변경해 주도록 하겠습니다.
index.js
window.addEventListener("popstate", () => {
renderContents();
});
이제 페이지 이동, 뒤로 가기, 앞으로 가기로 콘텐츠를 불러오게 되었습니다. 하지만 이러한 방식에는 문제가 있는데 동일한 경로로 이동을 하게 될 경우 history 스택에 같은 경로가 쌓이게 되고, 뒤로 가기 시 같은 페이지가 나오는 문제가 발생합니다.
띠라서 주소이동시에 같은경로라면 이동하지 않도록 작업을하도록 하겠습니다.
index.js
button.addEventListener("click", () => {
const targetUrl = "some?foo=bar";
const { pathname } = window.location;
//* 같은 URL 은 스택에 추가하지 않는다
if (targetUrl === pathname) {
return;
}
const locationChangeEvent = new CustomEvent("locationchange", {
composed: true,
detail: { href: "some" },
});
//* 주소변경 이벤트 Dispatch
window.dispatchEvent(locationChangeEvent);
});
자주 사용하게 될 주소 이동을 함수로 만들고, 내부 콘텐츠를 switch 내에서 require 하여 동적으로 콘텐츠를 불러올 수도 있습니다. 이러한 작업은 작업자가 필요에 따라 추가해 주시면 됩니다.
앞에서 다룬 내용을
https://github.com/jerrynim/vanillajs-router/tree/master
깃허브에 예제를 통해 올리도록 하겠습니다.
도움이 되었다면 좋겠네요ㅎㅎ
이런 vanilla 상태에서도 뭔가를 만드시는 게 진짜 실력자시네요. -:)