프로그래머스 과제 - Vanila JS로 SPA 구현하기

👀 문제를 마치며
페이지를 구현하고 컴포넌트를 구성하는 것은 어려운 일은 아니었으나, 데이터를 주고받고 라이브러리 없이 순수 자바스크립트로 직접 짜야하는 것은 생각보다 쉽지 않은 과정이었다. 그동안 사용했던 라이브러리의 원리도 더 이해하고 본질적인 프로그래밍에 대해 고민해보게되는 시간이었다.
물론 코드는 정말 보면 알겠지만, '2021 Dev-Matching: 웹 프론트엔드 개발자(하반기)' 기출 문제 해설 게시물을 많이 참고했다.
https://github.com/xohxe/spa-coffee.git
참고로 현재 70%정도 작업되었다. 실서버에서 오류 사항과 UI를 살짝 고쳐볼 예정이다.
1. 라우팅
그 동안 다른 프로젝트에서는 전혀 생각해보지 않은 부분이었다. React에서는 Router로, ExpressJS, Gatsby에서는 각각 프레임워크에 맞게 라우터를 구성했는데, 이걸 안쓰려고하니 막막했었다.
1-1. URL 라우팅
순수 바닐라 자바스크립트로 SPA를 구현하기위해서는 location.name을 이용하면 된다.
import ProductListPage from "./page/ProductListPage.js";
import ProductDetailPage from "./page/ProductDetailPage.js";
import CartPage from "./page/CartPage.js";
export default function App({ $target }) {
this.route = () => {
const { pathname } = location;
$target.innerHTML = "";
if (pathname === "/") {
new ProductListPage({ $target }).render();
} else if (pathname.indexOf("/products/") === 0) {
const [, , productId] = pathname.split("/");
new ProductDetailPage({ $target, productId }).render();
} else if (pathname === "/cart") {
new CartPage({ $target }).render();
}
};
this.route();
}1-2. 페이지 이동
a태그를 이용하면 간단한 거 아닐까 싶지만, SPA를 구현하는 것이 이 과제의 의도임을 기억해야한다.
history.pushState
1-3. 페이지 뒤로가기
뒤로가기구현 예정
2. API
2-1. 로컬 테스트시 CORS 오류
FE 개발자라면 한 번쯤 머리 싸매고 고민한다는 CORS 오류! 😭
Warning !
Access to fetch at ‘API Link’ from origin ‘http://localhost:5000’ has been blocked by CORS policy: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.
나도 이번에 겪어보면서 너무 답답했는데, 간단히 이유를 설명하자면 자바스크립트에서의 요청은 기본적으로 서로 다른 도메인에 대한 요청을 보안상 제한하기 때문이다.
에러 해결방법에는 여러가지가 있겠지만, 나는 두 가지를 시도해봤다.
2-1-1. no-cors
처음에는 에러메세지에 나온 내용대로 fetch 옵션에 mode :"no-cors"을 추가하면 해결될까했지만, data를 불러올 때 실패하였다.
// api.js
const API_END_POINT =
"https://h6uc5l8b1g.execute-api.ap-northeast-2.amazonaws.com/dev";
export const request = async (url => {
try {
const fullUrl = `${API_END_POINT}/${url}`;
const response = await fetch(fullUrl, {mode: "no-cors"});
if (response.ok) {
const json = await response.json();
return json;
}
throw new Error("API 통신 실패");
} catch (e) {
alert(e.message);
}
};
2-1-2. Proxy 설정
그런데 의외로 proxy 설정으로 쉽게 해결할 수 있다.
https://cors-anywhere.herokuapp.com/corsdemo주소를 API 주소 앞에 추가시켜주자.
// api.js
const API_END_POINT =
"https://cors-anywhere.herokuapp.com/https://h6uc5l8b1g.execute-api.ap-northeast-2.amazonaws.com/dev";
export const request = async (url, options = {}) => {
try {
const fullUrl = `${API_END_POINT}/${url}`;
const response = await fetch(fullUrl, options);
if (response.ok) {
const json = await response.json();
return json;
}
throw new Error("API 통신 실패");
} catch (e) {
alert(e.message);
}
};그리고 Request temporary access to the demo sercer 버튼을 눌러 허용만 해주면, 로컬에서도 정상적으로 작동한다. 물론 해당 문제는 테스트하는 실제 서버에서는 이런 문제를 겪지 않을 것이다. 나는 로컬에서 테스트해봤고 다음에도 이런 문제를 또 겪을 수 있을 것이기에 해당 케이스 문제해결방법도 추가해보았다.
2-2. 실 배포시 & 시험환경에서
2-3. 데이터 전달시 정의방법
3. 이벤트 처리
입력값이 변하거나 선택된 값이 바뀔 때마다 데이터를 변화되게 처리해야한다.
3-1. 클릭이벤트
버튼이 클릭된 후, 페이지 이동시 필요한 사항은 라우팅처리, localStorage에서 데이터 받아오기가 있다.
3-2. select가 change될 때
4. 로컬스토리지
장바구니로 데이터를 넘겨서 받아오려면 옵션이 선택될 때 setItem을 통해 localStorage에 저장하고, 장바구니 페이지에서는 getItem을 이용해서 데이터를 받아와야한다.
5. 페이지 구현
먼저 상태관리, 페이지를 렌더링하는 기본적인 구조는 다음과 같다.
export default function ProductListPage({ $target }) {
const $page = document.createElement("div");
$page.className = "ProductListPage";
$page.innerHTML = "<h1>페이지명</h1>";
this.state = {};
this.setState = (nextState) => {
this.state = nextState;
this.render();
};
this.render = () => {
$target.appendChild($page);
};
}