Vanilla JS로 SPA 구현하기

miniminion·2023년 1월 19일
0
post-thumbnail

넘블에서 주최한 vanilla js 신년메세지 챌린지

말그대로 프레임워크 없이 오직 vanilla js 만으로 SPA을 만드는 챌린지였다. (express, webpack은 허용되었지만 나는 순수 vanilla js로만 진행했다.) 처음으로 참가해보는 챌린지였기에 처음 경험해보는 것들이 있었다. 예를 들어,
1) 제공된 가이드에 따라 구현하기
2) 제공된 피그마의 디자인을 따라 구현하기
3) 제공된 REST API를 사용하기
등이 있었다.

물론 vanilla js를 사용하여 SPA를 제작해 본 것도 처음이었다. 나는 기존에 간단한 랜딩페이지 제작 경험이 있고, 배포까지는 아니지만 react를 활용하여 SPA를 제작해 본 경험이 있었다. React를 배우면서 라우팅도 사용하고 컴포넌트도 사용하고 여러가지 해보았지만 어떤 측면에서 SPA 제작에 유리하다는 것인지 와닿지 않았었는데 이번 챌린지를 통해 react의 편리함에 대해 몸소 체험할 수 있었다.

배운 점

1. Vanilla js로 라우터 기능 구현

History.pushState() 사용하여 페이지의 리로드 없이 주소만 변경하는 방식을 사용하였다. 기본 사용법은 다음과 같다.

let state = { 'page_id': 1, 'user_id': 5 };
let title = 'Hello World';
let url = 'hello-world.html';
 
history.pushState(state, title, url);

나는 state, title에는 각각 빈 객체와 빈값을 주었고 url에만 라우팅할 주소 값을 주는 방식으로 사용했다.

전반적인 라우팅은 다음과 같이 동작하도록 하였다.

1) 페이지 전환을 유도하는 버튼처럼 보이는 a 태그를 만들고 href 속성 값에 라우팅에 사용할 주소값을 부여함

<a id="newFeed" href="/post" onclick="route();">새 글 작성</a>

2) a 태그 클릭 시 라우팅

//a 태그 클릭시 연동된 함수, 새로고침을 방지하고 History.pushState()를 사용해 주소를 변경함
export const route = (event) => {
  event = event || window.event;
  event.preventDefault();
  window.history.pushState({}, "", event.target.href); 
  handleLocation();
};
//handleLocation()에서 참조하는 객체
export const routes = {
  404: "./pages/404.html",
  "/": "./pages/index.html",
  "/post": "./pages/post.html",
  "/edit": "./pages/edit.html",
  "/detail": "./pages/detail.html",
};
//History.pushState()를 통해 변경된 주소값에 해당하는 html파일을 불러와서 index.html의 특정 돔에 갈아끼움
export const handleLocation = async () => {
  const path = window.location.pathname;
  const route = routes[path] || routes[404];
  const html = await fetch(route).then((data) => data.text());
  document.getElementById("main-page").innerHTML = html;
};

2. JS 모듈 사용하기
이 챌린지를 통해 처음으로 자바스크립트 모듈을 사용 해 보았다. ES6 기반의 모듈 방식(script 태그에 type="module" attribute를 추가, import/export로 모듈 호출)을 사용했고, 그 과정에서 common js 모듈에 대해서도 살짝 공부했다.

3. MVC 패턴에 대한 고려
JS 모듈을 사용하면서, 처음에 했던 실수는 모듈끼리 서로를 import하는 것이었다... controller 역할을 해줄 JS가 없었기 때문이었다. 그래서 이를 해결하기 위해 index.js를 새로 만들면서 모듈들이 양방향으로 호출되는 일이 없도록 중재 해주었다. 최근에 django를 배우면서 MVC에 대해 경험해 본 적은 있었지만 내가 직접 설계해 본 적은 없었기에 좋은 경험이 되었다. MVC에 대해서는 좀 더 공부해야 할 것 같다.

4. 배포하기
제공받은 rest API가 http 였기 때문에 https-http mixed content error를 회피하기 위하여 배포할 웹 페이지 주소도 http로 통일 해야했다.
아래와 같이 meta 속성을 html에 추가해서 https로 업그레이드 시키는 방법도 있었지만 rest API가 https를 제공하지 않기 때문에 이 방법으로는 해결할 수 없었다.(net::ERR_CONNECTION_REFUSED 에러)

<meta
  http-equiv="Content-Security-Policy"
  content="upgrade-insecure-requests" />

여러시행 착오를 거쳤는데 깃허브 페이지 배포나 Netlify 경우는 기본이 https이기 때문에 나의 목적에 적합하지 않았고, AWS S3에서 퍼블릭 설정하여 http로 배포함으로서 배포에 성공할 수 있었다. AWS S3는 정적 파일(html,css,image,js 등)만을 업로드하여 배포하는 경우에 적합하다. 이번 경우에는 rest API를 사용하여 동적 구현을 했기 때문에 업로드 시에 정적 파일들만 있어서 AWS S3를 사용하면 비교적 쉽게 배포할 수 있었다.

**추가) 다른 넘블러 분의 도움으로 Netlify에서 리다이렉트를 통해 http api를 https 사이트에서 호출하는 방법을 알게 되었다!

가장 어려웠던 점

  1. 상세페이지 구현
    가장 어려웠고 가장 나중에 구현에 성공한 부분은 상세페이지였다. 상세페이지 연결 자체는 괜찮았지만, 상세페이지에서 새로고침 했을 때 제대로 라우팅이 되지 않는 문제가 있었다. express를 사용하지 않고 하다보니 라우팅에 주소에 정규표현식을 적을 수 없게 되면서 어려움을 겪었다. 그러다가 해시주소 형식을 사용하게 되면, window.location.pathname에서 #이전까지의 주소만 인식하는 것을 알게 되었다. 이 점을 이용하여 상세페이지를 새로고침까지 완벽히 구현하는 것에 성공할 수 있었다.

  2. 비동기화 문제
    자바스크립트 특성 상 코드들이 비동기적으로 진행되다 보니 순차적으로 진행되어야 하는 코드들을 동기화 시켜주는 것이 중요하였다. 특히 배포 후에 돔 생성이 지연되는 현상이 간혹 나타나면서 로컬 환경에서는 문제가 되지 않았던 비동기적 코드들이 간헐적으로 문제를 일으키기도 했다. 따라서, 라우팅 후에 불러와지는 html파일의 돔 생성이 완료되고 나서 돔에 요소를 붙여넣기하는 자바스크립트 코드들이 실행될 수 있도록 꼼꼼히 동기 처리를 해주었다. 이 부분을 해결하면서 자바스크립트 코드를 동기화 처리해주는 방법에 좀 더 익숙해진 것 같다.

더 시도해보고 싶은 점

  1. 뒤로 가기 시에 이전에 보던 부분으로 자동 스크롤
  2. 무한스크롤 구현

0개의 댓글