React나 Vue 등의 SPA에서 볼 수 있는 SPA 라우팅을 흉내내는 예제입니다.
참고로 window.location.pathname
을 이용한 URL 입력 기능을 실행하려면 SPA 라우팅을 지원하는 HTTP 서버가 필요합니다.
간단하게 설정할 수 있는 서버로 크롬 브라우저 플러그인인 Web Server for Chrome 을 소개합니다.
해당 앱을 실행하면 설정 창이 나옵니다. 여기서 index.html
파일이 있는 디렉토리를 선택하고 Show Advanced Options
를 클릭하면 고급 설정창이 나옵니다.
고급 설정창에서 Enable mod-rewrite (for SPA)
를 활성화합니다.
window.location 객체에서 정보 얻기
이 객체에서 URL과 관련된 다양한 정보를 얻을 수 있습니다.
URL에 포함된 pathname을 읽기
여기서 pathname
이란 http://abc.com/user/1 이라는 URL이 있다고 하면 /user/1
이부분이 pathname 입니다.
window.location.pathname
에서 값을 가져온 뒤 split()
하면 배열로 여러 정보를 얻을 수 있습니다. 이 중 필요한 정보만 취합해서 사용합니다.
// URL로 주소 입력하기 예) /user/1 let [, service, value] = window.location.pathname.split("/") // ["" ,"user", "1"]
이렇게 하면 service
에 user
, value
에 1
이 전달됩니다.
URL을 입력받기
인물 정보를 보내준는 외부 API가 있고 URL에 /user/1
을 입력하면 그 사람에 대한 정보를 보여주고 싶다고 할 때, 다음과 같이 fetch
정보에 아이디를 넘겨주면 됩니다.
if (service === "user") { fetchUser(value) } async function fetchUser(userId) { try { const response = await fetch(`https://reqres.in/api/users/${userId}?delay=1`) const json = await response.json() const data = json.data // ... 이하 생략 ... // } catch(err) { alert("오류가 발생했습니다.") } }
이렇게 하면 URL을 직접 입력했을 때, 그 사람에 대한 정보를 보여주게 됩니다.
페이지는 이동하지 않고 URL만 변경하기
window.history.pushState()
를 이용합니다. 이 기능은 표면적인 URL 변경 뿐만 아니라 뒤로가기 기능도 작동하므로 실제 웹 페이지를 탐색하는 듯한 느낌을 주게 됩니다.
이 예제에서 NEXT나 PREV 버튼을 누르면 웹 페이지가 이동하는 것이 아닌, 네트워크에서 정보만 다시 받아와 페이지 내의 정보를 교체하는 형식으로 진행합니다. (전체 코드 참고)
인물 정보는 교체되었지만 URL이 그대로라면 어색할 것입니다. 따라서 URL 교체 작업이 필요합니다.
pushState()
의 사용법은 다음과 같습니다.
- 1번째 파라미터(필수)는 다음 주소로 넘길 객체를 설정합니다.
history.state
를 사용하면 불러올 수 있습니다. 하지만 지금은 사용할 일이 없기 때문에null
로 입력합니다. - 2번째 파라미터(필수)는 브라우저 창의 제목을 바꾸는데 현재 대부분의 브라우저가 지원하지 않습니다. 빈 스트링을 입력합니다.
- 3번째 파라미터(선택사항)는 바꿀 주소를 입력합니다. 예를 들어 도메인이
xxx.com
인 경우, 앞에/
를 추가하면 절대 경로가 되어서 현재 위치가 어떻든간에xxx.com/custom/url
로 변경합니다. 반대의 경우 상대 경로로 추가되기 때문에 만약 현재 위치의 url이xxx.com/user/1.html
인 경우 최종 주소는xxx.com/user/custom/url
이 됩니다. 원래 생략 가능하지만 지금 목적은 주소를 바꾸는 것이므로 여기에 주소를 입력합니다.
function setURL(value) { history.pushState(null, "", value) fetchUser(value) }
// 참고: 버튼 이벤트 코드 const btnNext = $(".btn-next") const btnPrev = $(".btn-prev") btnNext.addEventListener("click", e => { value = Number(value) + 1 setURL(value) }) btnPrev.addEventListener("click", e => { value = Number(value) - 1 setURL(value) })
전체 코드
function $(selector) { return document.querySelector(selector) } const id = $(".id") const profile = $(".profile") const email = $(".email") const fullname = $(".fullname") const loading = $(".loading") const container = $(".container") // URL로 주소 입력하기 let [, service, value] = window.location.pathname.split("/") if (service === "user") { fetchUser(value) } function setURL(value) { history.pushState(null, "", value) fetchUser(value) } async function fetchUser(userId) { loading.style.display = "flex" try { const response = await fetch(`https://reqres.in/api/users/${userId}?delay=1`) const json = await response.json() const data = json.data loading.style.display = "none" container.style.display = "block" profile.src = data.avatar email.textContent = data.email email.setAttribute("href", `mailto:${data.email}`) fullname.textContent = `${data.first_name} ${data.last_name}` id.textContent = `${data.id}. ${data.first_name} ${data.last_name}` } catch(err) { alert("오류가 발생했습니다.") value = 1 setURL(value) } } const btnNext = $(".btn-next") const btnPrev = $(".btn-prev") btnNext.addEventListener("click", e => { value = Number(value) + 1 setURL(value) }) btnPrev.addEventListener("click", e => { value = Number(value) - 1 setURL(value) })
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Show User</title> <style> .loading { display: none; width: 100%; height: 100%; justify-content: center; flex-direction: column; align-items: center; position: absolute; left: 0; top: 0; background-color: rgba(0, 0, 0, 0.1) } .container { display: none; } .profile { border-radius: 5px; } </style> </head> <body> <div class="loading">loading...</div> <div class="container"> <h3 class="id"></h3> <img src="" class="profile" alt=""> <ul> <li>email: <a href="mailto:" class="email"></a></li> <li>name: <span class="fullname"></span></li> </ul> <button class="btn-prev">PREV</button> <button class="btn-next">NEXT</button> </div> <script> // ............... // </script> </body> </html>
0개의 댓글