https://programmers.co.kr/skill_check_assignments/199
프로그래머스
코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.
programmers.co.kr
오랜만에 바닐라 JS 개발을 해보고 싶었는데, 마침 SPA 개발 과제가 있길래 해보았다.
그 과정에서 긴 시간 고전한 주제에 대해 적고자 한다.
결론적으로, 난 완벽한 구현에 성공하지 못했다.
첫 번째로 SPA 라우팅 처리를 vanilla js 로 해본 게 처음이라 고전했고 (쉬운 건데 삽질했다)
두 번째로 js 에서 HTML element 를 만들 때 createElement 를 하나하나 일일히 했다. (미쳤나봄)
해답을 보니 innerHTML = `<div></div>` 식으로 편하게 했더라.
React 를 쓰는 실무에서 워낙 할 일 없던 거라 생각을 못했다.
SPA 라우팅 처리
SPA는 별도의 서버 사이드 렌더링 없이, 경로 별로 innerHTML 을 변경해주면 된다.
즉, 라우팅 처리가 굉장히 중요한 부분이라고 생각했고, history.pushState 와 popState 가 해결책이 되었다.
그 과정에서 한시간 가량 삽질한 부분에 대해 말해보고자 한다.
index.html
<html>
<head>
<title>커피캣 스토어</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<main class="App">
</main>
<script src="src/index.js" type="module"></script>
</body>
</html>
src 폴더의 index.js 를 body 태그 밑에 불러온다.
src/index.js
import App from "./App.js"
const start = () => {
return App();
}
start();
create-react-app 의 구조가 익숙해서 이런 식으로 했는데, 의미가 없었던 거 같다.
src/App.js
import ProductDetail from "./pages/ProductDetail.js";
import ProductList from "./pages/ProductList.js";
import { renderAppContent } from "./utils.js";
const App = async () => {
const app = document.getElementsByClassName("App")[0];
// 맨 첫 페이지는 list 이므로, App 에 ProductList를 렌더한다.
renderAppContent(await ProductList()); // ProductList 함수를 실행시킨 결과값(HTML element)를 app 에 렌더한다
history.pushState({page : "list"}, '', `.`); // 렌더하면서, list 로 갔다는 표시로 pushState 를 해준다.
const onPageChange = async () => {
switch(history.state.page) {
case "list":
renderAppContent(await ProductList());
break;
case "detail":
renderAppContent(await ProductDetail({id : history.state.id}));
break;
default:
return;
}
}
window.addEventListener('popstate', onPageChange)
return app;
}
export default App;
pushState 는
history.pushState( data, title, path ) 의 형태로 이루어진다.
이 때 실제로 앱 전체를 다시 불러오는 형태가 아닌, url 의 path 만 변경시키면서 data 를 history 스택에 push 하는 방식으로 진행된다.
pushState 를 하게 되면 뒤로 가기 버튼이 활성화가 된다. (실제로 하나 이동했다는 말. history.go(1) 과 같은 느낌이다 )
내가 삽질한 부분은, 첫 화면에 들어왔을 때, history.pushState 를 해주지 않고, 리스트의 항목을 클릭했을 때만 pushState 를 해준 것이다.
- 첫 화면(리스트)이 렌더 됐음에도 pushState 를 해주지 않았기 때문에 null 이 저장됨
- 리스트의 항목 중 하나를 클릭해서, pushState 를 통해 detail 페이지로 이동함 ( 잘 됐음 )
- 뒤로 가기 하면 popstate 이벤트가 발생하는데, 이 때 state 가 null 이었음.
- 근데 앞으로 가기를 다시 누르니까 디테일 페이지로 이동하면서, 디테일 페이지에 전해주려 했던 state 들이 잘 찍혔음 (이것 때문에 헷갈렸음)
나는 바보같이 pushState 를 처음에 해줘야 하는 지 몰랐다. 그래서 계속 null 이 나타나서 한시간 가량 날렸다. ㅋㅋ
저 한줄을 추가하자 거짓말 처럼 잘 됐다.
두 번째 실수 : innerHTML = <div> 를 쓰지 않음
나의 ProductDetail 페이지 코드를 보면 ( 다 안 읽는 걸 추천한다 )
import { makeElement } from "../utils.js"
const ProductDetail = async ({id, name, imageUrl, price}) => {
const productDetailPage = makeElement("div", "ProductDetailPage");
const productDetailPageTitle = makeElement("h1");
productDetailPageTitle.innerText = `${name} 상품 정보`;
productDetailPage.appendChild(productDetailPageTitle);
const productDetail = makeElement("div", "ProductDetail");
const produdctImage = makeElement("img", "", {
src : imageUrl
})
productDetail.appendChild(produdctImage);
const productDetailInfo = makeElement("div", "ProductDetail__info");
const productDetailInfoTitle = makeElement("h2");
productDetailInfoTitle.innerText = `${name}`;
productDetailInfo.appendChild(productDetailInfoTitle)
const productDetailPrice = makeElement("div", "ProductDetail__price");
productDetailPrice.innerText = `${price}원~`;
productDetailInfo.appendChild(productDetailPrice)
const productSelectElement = makeElement("select");
const defaultOption = makeElement("option");
defaultOption.innerText = `선택하세요.`;
productSelectElement.appendChild(defaultOption);
productDetailInfo.appendChild(productSelectElement)
const selectedOptions = makeElement("div", "ProductDetail__selectedOptions");
const selectedOptionsTitle = makeElement("h3");
selectedOptionsTitle.innerText = `선택된 상품`;
const selectedOptionsUl = makeElement("ul");
selectedOptions.appendChild(selectedOptionsUl);
productDetailInfo.appendChild(selectedOptions);
productSelectElement.onchange = () => {
const selected = productSelectElement.options[productSelectElement.selectedIndex].value
console.log(selected)
productSelectElement.value = productSelectElement.options[0].value;
const selectedOption = makeElement("li");
}
try {
const {productOptions} = await (await fetch(`https://uikt6pohhh.execute-api.ap-northeast-2.amazonaws.com/dev/products/${id}`)).json();
productOptions.forEach((productOption) => {
const option = makeElement("option");
option.innerText = `${name} ${productOption.name}`;
if(productOption.price >= 0) {
option.innerText += `(+${productOption.price}원)`;
}
if(productOption.stock === 0) {
option.innerText = `(품절) ${option.innerText}`;
}
productSelectElement.appendChild(option);
})
} catch(e) {
}
productDetail.appendChild(productDetailInfo);
productDetailPage.appendChild(productDetail)
return productDetailPage;
}
export default ProductDetail;
너무 힘들었다.
왜 productDetailPage.innerHTML = `blahblah` 를 생각하지 못했을까?
해답을 보고 힘이 다 빠져버렸다..
데브 매칭은 바닐라 JS 로 항상 이루어졌고, 앞으로도 그럴 것 같다. (프레임워크 별로 차이가 있는데, 리액트만 받을 순 없자나.. )
배울 게 아직도 많다는 걸 여실히 깨달은 하루였다.
이 과제에서의 남은 점은, UI 조작의 자잘한 부분 ( 가격에 콤마찍기, 선택 물품 가격 계산하기) 와 주문하기 ( 로컬 스토리지 사용하기 ) 인 것 같은데, 이것들은 쉬우니까 더 안봐도 될 것 같다.
SPA 구현을 해보는 데 굉장히 좋은 기회가 되었다.
고양이 사진첩 만들기 (상반기 문제) 도 풀어 봐야지.
'Front-End > HTML,CSS' 카테고리의 다른 글
이건 보고 HTML 짜자! 깔끔한 HTML을 만들기 위한 HTML 코드 컨벤션 (0) | 2021.03.03 |
---|
최근댓글