[프로그래머스 과제관] 쇼핑몰 SPA

엘리(Ellie)·2023년 6월 2일
0

프로그래머스 과제관의 쇼핑몰 SPA 과제 테스트를 복기하며 주요 요구 사항과 그에 대한 해결 방법을 정리했습니다.
전체 코드는 여기에서 보실 수 있습니다.

주요 요구사항 및 해결 방법

SPA 구현

상품 목록, 상품 상세정보, 장바구니 세 페이지를 SPA로 구현하라.

SPA를 VanillaJS로 구현하기 위해서는 3가지 구성요소가 필요합니다.

  1. router - 현재 url을 보고 적절한 페이지를 그린다.
  2. CustomEvent - 페이지를 이동하려고 할 때 브라우저 이벤트가 아닌 CustomEvent를 이용해 라우팅을 처리한다.
  3. server - 브라우저에서 웹 페이지를 요청할 때 항상 index.html을 반환한다. (라우팅을 클라이언트 측에서 처리하게끔)

사실 1번 2번은 클라이언트 측 과제이지만 3번은 웹 페이지를 서빙하는 서버가 있는 경우에 의미가 있습니다. 과제 테스트 상으로는 서버를 건드릴 수 없기 때문에 router, custom event만 잘 처리하면 되지만 공부를 위해 재구현을 할 때에는 서버까지 직접 구현 해 보았습니다.

화면을 이동하는 2가지 케이스는 주소창에 url을 직접 입력해서 이동하는 방식과, 액션을 통해 페이지를 이동하는 방식이 있습니다.

Case 1. url로 페이지 접근

url로 페이지에 접근하는 경우, SPA를 지원하기 위해서는 서버의 도움이 필요합니다. Client-side Routing을 할 수 있도록 모든 페이지 요청에 대해 index.html을 전달 해 주어야 합니다. 그 다음은 클라이언트에서 라우팅을 직접 처리합니다.

image

  1. 브라우저가 서버로 url에 해당하는 페이지 요청
  2. 서버는 index.html 파일 전달
  3. 클라이언트에서 router가 현재 url에 해당하는 route를 찾음
  4. route에 해당하는 페이지 렌더링

Case 2. action으로 페이지 접근

액션을 통해 페이지를 이동하려는 경우, 서버로 페이지 요청을 보내는 브라우저 이벤트 대신 CustomEvent를 만들어야 합니다.

  1. [상품 목록]에서 상품 클릭
  2. CustomEvent 발생
    ex) new CustomEvent('urlchange', { detail: path })
  3. CustomEvent 핸들링
    • 브라우저 history 변경
    • route 찾아서 해당하는 페이지 렌더링

이렇게 2가지 케이스를 처리해서 SPA를 구현했습니다.

API 요청

api를 요청해서 받아온 데이터를 화면에 그려라.

프로그래머스 웹 환경에서 api를 요청하면 별 문제 없이 데이터를 받을 수 있지만, 로컬 환경에서 해당 주소로 api를 요청하면 CORS 문제로 받아올 수 없었습니다.
임시로 클라이언트 측에 mock data를 만들어서 요청 없이 바로 데이터를 전달하는 방식도 생각했지만, 직접 로컬 서버를 만드는 방식이 url로 페이지 접근하는 SPA도 구현할 수 있고 직접 데이터를 조작할 수 있어 자유도가 높을 것 같아서 로컬 서버를 만들기로 했습니다.

server

로컬 서버는 용도 별로 포트를 분리해서 웹 페이지 요청은 3000 포트, api 요청은 8080 포트에서 처리하도록 했습니다.

클라이언트 라우팅을 지원하기 위해 3000 포트로 들어오는 모든 get 요청에 대해 index.html를 서빙합니다.

webpage.get('/*', (req, res) => {
  res.sendFile(path.resolve(__dirname, '../client', 'index.html'));
});

8080 포트로 들어오는 api 요청도 요청에 맞게 적절한 데이터를 서빙하면 됩니다.
이 때, 데이터를 요청하는 웹 페이지의 포트번호(3000)와 서버의 포트번호(8080)가 다르기 때문에 CORS 이슈가 발생합니다. 이를 위해 모든 CORS 요청을 허용해 주었습니다.

const cors = require('cors');

const api = express();
api.use(cors());

client

이제 클라이언트에서는 다음과 같이 8080 포트로 요청을 보내고 받을 수 있습니다.

const baseUrl = 'http://localhost:8080';
const api = {
  async getProducts() {
    const res = await fetch(baseUrl + '/products');
    return await res.json();
  },
  async getProduct(id) {
    const res = await fetch(baseUrl + `/products/${id}`);
    return await res.json();
  },
};

ProductDetailPage 옵션 선택 & 수량 변경

상품의 옵션을 선택하고, 수량을 변경하는 기능을 구현하라.

<select> 태그의 옵션 선택 이벤트를 감지하기 위해 onchange 핸들러를 등록했습니다.

// 상품 옵션 선택
$select.onchange = (e) => {
  // 이미 선택된 옵션이면 수량만 증가
  // 선택된 옵션이 없으면 새로 추가
};

마찬가지로, 수량을 변경하기 위해서는 <input> 태그의 onchange 핸들러를 등록하면 되는데 리스트 안에 <input> 태그가 여러 개 일 수 있기 때문에 이벤트 버블링을 이용해 상위 요소에 핸들러를 한 번만 설정 해 주었습니다.

const $selectedOptions = this.$target.querySelector('.ProductDetail__selectedOptions');
$selectedOptions.onchange = (e) => {
  if (e.target.tagName !== 'INPUT') return;
  
  ...
  
  // 상품 개수 제한
  // - 상품 개수는 1보다 작을 수 없고, 재고(stock)를 넘길 수 없다.
  if (e.target.value < 1) {
    e.target.value = 1;
  } else if (e.target.value > selectedOption.stock) {
    e.target.value = selectedOption.stock;
  }
  
  ...
}

맺으며

테스트를 마친 후, 웹 페이지 구현 능력을 판별할 수 있는 적절한 난이도와 시간이었다고 생각했습니다.
테스트 제목이 '쇼핑몰 SPA'였기 때문에 SPA 구현 방법을 익히고 들어갔다면 나머지는 웹 페이지의 필수 기능 구현에 대한 요구사항으로 크게 어려울 것이 없었습니다. 하지만 익숙하지 않은 온라인 에디터를 사용해야 한다는 점과, 3시간이라는 시간 제약이 있기 때문에 모든 요구사항을 만족하기 위해서는 요구사항에 대해 막힘 없이 바로 구현을 할 수 있어야 했습니다.
불안정한 와이파이와 싸우느라 좀 더 시간이 부족했다는 것을 감안하더라도 몇몇 요구사항에 대해 시간을 지체하여 완성하지 못했기 때문에 아쉬움이 많이 남았던 테스트였습니다.

다양한 라이브러리와 프레임워크의 도움으로 웹 개발을 하면서 그 아래에 숨겨져있는 기본 동작 방식을 점점 잊어버리게 됩니다. 지금은 실제 테스트를 위한 연습이지만 시험이 목적이 아닐 때에도 한 번씩 이런 테스트를 통해 가볍게 기억을 되살리는 것도 좋겠다 싶었습니다.

profile
신기하고 재미있는 것 만들기를 좋아합니다 :)

0개의 댓글