MVC패턴과 재사용성을 고려한 Component 기반 Web UI 구현

이글은 코드스쿼드 마스터즈코스 레벨3을 수료한 이관형님이 작성한 개발 후기입니다. 프레임웍 의존성 없는 컴포넌트방식의 웹페이지 개발시 참고가 될 수 있습니다.


Introduction

한 달 전 코드스쿼드 레벨3 교육과정에서 아래 화면을 구현해보라는 미션을 받았다.

크게 autocomplete, scroller, infinite slide, main slide, best banchan 5가지 컴포넌트로 나뉜 웹페이지이다. (실제 배민찬 홈페이지에서는 autocomplete 기능은 지원하지 않는다.)

5가지 컴포넌트들은 각각의 기능을 가지고 서로 통신이 필요하지 않은 독립적인 활동을 하였다.

최종적으로 만든 결과 DEMO와 Code는 다음과 같다.
View on GitHub
Demo Page

실제 설계는 아래와 같다.

UML diagram

Controller 1개와 MainSlideView 1개, BestBanchanView 1개, ScrollerView 1개, InfiniteSlideView 3개, AutoCompleteView 1개로 구성하였다.

Controller

컨트롤러의 역할은 mainSlide, bestBanchan, infiniteSlide, scroller, autoComplete를 컨트롤하는 것이다.

  • mainSlide는 fetchMainSlide method를 통해 ajax로 mainSlide 이미지들을 받아 rendering하고 slidesPrev, slidesNext, slidesDots가 click 됐을경우 event들을 binding한다. (moveSlide, selectSlide)
  • bestBanchan은 fetchBestBanchan method를 통해 ajax로 foodData를 받아 rendering하고 foodTab이 click됐을때 event들을 binding한다.
  • infiniteSlideBanchan은 fetchInfiniteSlide method를 통해 ajax로 foodData를 받아 rendering하고 slidesPrev, slidesNext가 click 됐을 경우와 slides가 transitionend 됐을 경우 event들을 binding한다. (moveInfiniteSlide, resetInfiniteSlides)
  • scroller는 up이나 down button이 click 됐을경우 event들을 binding한다. (moveScroller)
  • autoComplete는 최근검색어와 추천검색어 rendering을 담당한다. 키보드가 눌렸을 경우, 제출 될 경우, searchbar가 click 됐을 경우나 click 되지 않았을 경우, suggestion element이 click 됐을 경우, mouseover 됐을 경우 event들을 binding한다. (pressAutoComplete, submitKeyword, fetchHistory)

MainSlideView

  • slidesPrev, slidesNext event에 1초 동안 throttling하여 중복 click event를 방지하였고 move 이벤트를 만들어 현재의 index 값과 direction 값을 방출한다.
  • slidesDots event에 1초 동안 throttling하여 중복 click event를 방지하였고 selectDot 이벤트를 만들어 현재 element의 index 값을 방출한다.
  • showSlide, hideSlide method는 fadein, fadeout 애니메이션 효과를 이용하였다.

BestBanchanView

  • foodTab, foodBoxList, foodBox, selectedFood method를 통해 순차적으로 rendering한다.
  • foodTab이 click 됐을 경우 event delegate를 통해 현재의 delegateTarget을 확인하고 그의 해당하는 category_id를 확인하여 rendering한다.

ScrollerView

  • up, down Button이 click 됐을 경우 event delegate를 통해 현재 click된 button을 확인하고 move 이벤트를 발생시켜 direction 값을 방출한다.
  • window가 scroll 됐을 경우를 확인하여 scrollY가 90이상일 경우에만 scroller 컴포넌트를 보여준다.

InfiniteSlideView

  • slidesPrev, slidesNext event에 1초 동안 throttling하여 중복 click event를 방지하였고 move 이벤트를 만들어 현재의 index 값과 direction 값을 방출한다.
  • transitionend 이벤트가 발생할 경우 현재의 direction 값을 방출한다.
  • foodBoxList와 foodBox, slides method를 통해 순차적으로 rendering한다.
  • showSlides method를 통해 translate 효과로 현재 선택된 index의 반찬들을 보여준다.

AutoCompleteView

  • keyup 이벤트가 발생할 경우 press 이벤트를 만들어 target.value와 keycode, 현재 선택된 element의 유무를 방출한다.
  • submit button이 click 될 경우 입력된 keyword를 방출한다.
  • 현재 autocomplete 컴포넌트가 닫혀있고 입력된 keyword가 없을 경우에 searchbar가 click될 경우 최근 검색어 목록을 방출한다.
  • suggestion이 click될 경우 event delegate를 통해 현재의 delegateTarget을 확인하고 현재 선택된 element를 setting한다.
  • searchbar가 클릭되지 않을 경우 autocomplete 컴포넌트를 닫아준다.
  • suggestion에 mouseover나 mouseout 이벤트가 발생할 경우 선택된 element를 setting한다.

Development

개발 과정에서 많은 시행착오가 발생하였는데 그 중 기억이 나는 것만 간추려보았다.

먼저 best slide를 개발 당시에 발생한 문제이다.

best slide의 foodBox를 컴포넌트 형태로 template literal를 활용해서 구현하였는데 코드가 아래와 같이 가독성이 떨어지는 문제점이 발생하였다.

그래서 handlebars를 활용하여 아래와 같이 변경하였더니 좀 더 간결해진 모습을 볼 수 있었다.

infinite slide 컴포넌트를 구현할 때부터는 코드의 가독성이 떨어져서 MVC 패턴을 적용하여 구현하였다. 개발 시간이 조금 걸리긴 하지만 분리하고 나니 추후 기능 구현시 확장하기 편리하였다.

infinite slide 컴포넌트를 추가할 때 View 안에 코드량이 많아져 한 눈에 볼 수 없었다. 또한 infinite slide 객체 3개가 필요했지만 따로 생성할 수도 없었다. 그래서 infinite slide class를 view class에서 분리하고 3개를 생성하여 controller에 연결시켰다.

infinite slide의 애니메이션 개발 시에 초기에는 움직일 때마다 움직이는 방향에 노드를 추가하는 방식으로 생각했었다. 그런데 DOM 접근 비용이 많이 발생하는 방법이었다. 그래서 초기에 총 5개의 슬라이드를 만들어 왼쪽이나 오른쪽 threshold로 이동할 때 index를 중앙으로 초기화하는 방식으로 변경하였다.

또한, 슬라이딩시 현재 index 값을 보관해야했다. 그래서 View에 의존성이 많고 각각의 View가 독립적이라고 생각하여 View안에 state로 저장하였다.

controller에서 이벤트를 binding하고 View에서 callback으로 target data를 넘겨주는 방식에서 CustomEvent를 만들어 emit하는 방식으로 변경하였는데 지금은 View와 Model이 1대1 매핑관계이기에 이점이 존재하지 않았다.

scroller 개발 시에 초기에는 setTimeout으로 구현하였는데 animation주기를 16.6ms(60fps) 미만으로 하는 경우 불필요한 frame 생성이 되는 등의 문제가 있어 requestAnimationframe으로 구현하였다.

이 외에도 keyboard control, delegation과 bubbling/capturing 등의 개념이 필요한 autocomplete 구현, 간단한 node 웹서버 API 구현 등을 하기도 하였다.

Conclusion

MVC 디자인 패턴을 적용해 본 결과 코드가 간결해진 느낌을 받았다. View에서 할 일 Controller가 할 일을 나눠 배민찬 홈페이지 내의 기능을 여러 개의 View로 나눠 View가 추가될 때마다 손쉽게 코드를 작성할 수 있어 확장하기 편했다. 그러나 몇 가지 고민들도 존재했다.

  1. View가 많아질수록 Controller가 비대해지는 경향을 보인다. 이 경우 Controller도 분리하는 방법도 생각해볼 수 있겠다.
  2. ajax call을 controller에서 해야 하는 일인지 view에서 해야 하는 일인지 고민이 있었다.
  3. event를 binding하고 view에서 target data를 받아서 핸들링할때 그 값을 방출시키고 다시 controller에서 받아 view method를 call할 것인지 아니면 view에서 바로 처리할지에 대한 고민도 있었다.

autocomplete나 event delegation, infinite slider는 모두 구현한 npm 라이브러리가 존재한다. 그러나 있는 걸 다시 만드는 것은 배움의 관점에서 봤을 경우에는 좋은 방법이라는 구글 엔지니어인 Philip Walton의 말은 틀리지 않은 것 같다. 시간은 오래 걸렸지만 직접 개발하면서 클로저나 스코프체인, 비동기통신, 애니메이션 등 실제 구현에 필요한 자바스크립트 원리를 깊이 있게 이해할 수 있는 계기가 되었다.

다음에는 최신 애플리케이션 아키텍처인 MVVM을 프로젝트에 적용하고 각각의 장단점을 공부해보고 싶다.

Demo

View on GitHub

Demo Page

Reference

Model–view–controller