프로그래머스 커피 주문 페이지 만들기 - 개발 (컴포넌트 및 router 개발)

Z6su3·2022년 5월 9일
0

🐇 컴포넌트 구조

컴포넌트는 크게 3개로 구분된다.

  • 상품목록 페이지
  • 상품상세 페이지
  • 장바구니 페이지

이와 요구사항을 참고하여, router 및 api 그리고 localStorage관리에 대한 파일을 추가하여 다음과 같이 구조를 만들 수 있다.

/src ← /components ← ProductListPage.js
← ProductDetailPage.js
← CartPage.js
← /lib ← LocalStorage.js
← /router ← router.js
← App.js, main.js

🥕 기본 틀 제작

기본적인 구성을 진행해주고 App.js와 각 페이지들의 기본 틀을 만들어준다.

index.html을 통해 각 페이지에 필요한 마크업 요소를 추출하여 기본적으로 바인딩 해준다.

이때, 추후 라우터 기능을 확인하기 위한 테스트용도이므로, 간략하게 작성해준다.

index.html

<html>
  <head>
    <title>커피캣 스토어</title>
    <link rel="stylesheet" href="src/styles/style.css" />
    <script type="module" src="src/main.js"></script>
  </head>
  <body>
    <main class="App"></main>
  </body>
</html>

main.js

import App from "./App.js";

new App(document.querySelector(".App"));

App.js

export default function App($app) {
  this.router = () => {};
  this.init = () => {};
  this.init();
}

ProductListPage.js

export default function ProductListPage({ $app, initialState }) {
  this.state = initialState;
  this.$target = document.createElement("div");
  this.$target.className = "ProductListPage";
  $app.appendChild(this.$target);

  this.render = () => {};
}

ProductDetailPage.js

export default function ProductDetailPage({ $app, initialState }) {
  this.state = initialState;
  this.$target = document.createElement("div");
  this.$target.className = "ProductDetailPage";
  $app.appendChild(this.$target);

  this.render = () => {};
}

CartPage.js

export default function CartPage({ $app, initialState }) {
  this.state = initialState;
  this.$target = document.createElement("div");
  this.$target.className = "CartPage";
  $app.appendChild(this.$target);

  this.render = () => {};
}

🐇 router

router의 간단 작동 원리는 다음과 같다.

  • 1) history.pushState() 실행
  • 2) location의 pathname을 통해 매핑되는 component 판별
  • 3) component 생성 및 렌더
  • 4) popstate 이벤트 할당

여기서 1)과 3)은 App.js에서 그리고 2)는 router.js에서 판별할 수 있도록 구분했다. 기존 해설을 참조하면 위 1)은 각 component에서 발생하고 2)와 3)이 App.js에서 실행되는데, 필자의 this.router()를 제거하고 각 component에서 직접 1)을 실행하여도 무관하다.

굳이 App.js에서 this.router를 만든 이유는, 특정 라우터 요소가 클릭됐을 때, history.pushstate()와 App의 리렌더링이 같이 일어남을 이해하기 위해 추가하였다.

구현하기 전, 사용하는 함수들에 대해 파악하려면 router을 참조하라.

순서대로 구현하면 다음과 같다.

🥕 1) history.pushState() 실행

App.js

export default function App($app) {
  this.router = (data, unused, url) => {
    history.pushState(data, unused, url);
    this.init();
  };
  this.init = () => {};
  this.init();
}

🥕 2) location의 pathname을 통해 매핑되는 component 판별

router.js

import ProductListPage from "../components/ProductListPage.js";
import ProductDetailPage from "../components/ProductDetailPage.js";
import CartPage from "../components/CartPage.js";

const routes = [
  { path: "/web/", component: ProductListPage },
  { path: "/web/products", component: ProductDetailPage },
  { path: "/web/cart", component: CartPage },
];

const routeFind = (path) => {
  return routes.find((route) => route.path === path);
};

export const router = () => {
  let { pathname } = location;

  if (pathname === "/") {
    pathname = "/web/";
  } else if (pathname.includes("/products/")) {
    pathname = "/web/products";
  }

  return routeFind(pathname);
};

pathname과 매핑되기 위한 route component들을 routes와 같이 미리 매핑한 뒤 찾으면 된다.

  • pathname = “/” : localhost:~ 로 접속할 경우 메인 페이지 접속과 같으므로 /web으로 변환
  • pathname = “/products/:productId” : 상품의 Id가 존재하는 경우 /web/products로 통일화

/web을 붙여 매핑을 진행하면 /web과 /web/cart로 오는 pathname에 대한 처리를 해주지 않아도 됨.

위 방식을 여러 방법으로 수정해보려 햇으나, 가장 간결한 방법이라 생각했습니다.

🥕 3) component 생성 및 렌더

App.js

export default function App($app) {
  this.router = (data, unused, url) => {
    history.pushState(data, unused, url);
    this.init();
  };
  this.init = () => {
    $app.innerHTML = "";

    const routeComponent = router();

    let componentData = [];

    switch (routeComponent.path) {
      case "/web/":
        break;
      case "/web/products":
        break;
      case "/web/cart":
        break;
    }

    new routeComponent.component({
      $app,
      initialState: componentData,
    });
  };
  this.init();
}

App에 렌더링 되기전, 기존 요소를 지우고 새로운 routeComponent를 생성해줍니다.

추가적으로, 각 component에 알맞는 데이터를 넣기 위해 componentData를 생성하고 추후 switch구문에서 데이터를 불러오도록 합니다.

🥕 4) popstate 이벤트 할당

popstate를 통해 브라우저 뒤로가기를 감지하고, 이벤트가 발생하면 App을 재 랜더링 하기위해 this.init을 EventListener에 할당해줍니다.

App.js

export default function App($app) {
  this.router = (data, unused, url) => {
    ...
  };
  this.init = () => {
    ...
  };
  this.init();

	window.addEventListener("popstate", this.init);
}
profile
기억은 기록을 이길수 없다

0개의 댓글