ECMAScript | TypeScript

FE개발자로서 못해준 이야기 2 - 컴포넌트

partner_jun 2022. 1. 18. 12:28

컴포넌트에 대해

FE 개발을 할 때 제일 중요한 것은 컴포넌트다. 사실 거의 모든게 컴포넌트다. 막말로 컴포넌트를 대충 끄적이면 프로젝트 개발이 끝난다. 하지만 시간이 지날수록 생산성은 저하되고 심리적 부담감은 커진다. 유지보수와 개발을 진행하며 기능이 추가되고 변경되어 코드가 어려워지는 것은 어쩔 수 없지만, 최대한 그 시점을 늦추기 위해서 나는 아래 내용들을 고려한다.

 

 

컨텐츠를 기준으로 구조를 만들자

프론트엔드는 결국 기획과 디자인, 그리고 백엔드에 종속되어 있다. 특히 FE 개발자로서 가장 자주 충돌하는 대상은 기획자다. 그리고 기획자는 개발자가 아니다. 이 버튼을 저리로 옮기는 '단순한' 작업에 왜 그렇게 시간이 오래 걸리는지, 어렵다고 하는지 이해할 수 없다.  컴포넌트의 데이터가 어쩌고 저쩌고 하는 개발자가 마음에 안 들 뿐이다. 간혹 이해한다 해도 그것은 그 기획자가 특이한 것이지, 기획자의 당연한 업무 처리 방식이 아니다. 그렇기 때문에 개발자의 데이터 처리를 위한 시선이 아니라 컨텐츠를 배치하는 기획자의 입장에서 아키텍처링을 해야 한다.

 

컨텐츠를 중심으로 개발한다면 데이터 처리에 있어 중복되거나 불필요한 연산이 늘어날 수 있지만, 기획/디자인의 변경 요청에 혼쾌히 가능하다고 답할 수 있다. 데이터가 어쩌고 연산이 어쩌고 하는 이해 시키기 힘든 말을 하지 않을 수 있다는 것이다. 그것이 개발자로서의 프라이드를 깎아먹는 일이라고 생각할 수 있다. 하지만 프로젝트의 유지보수를 편하게 하는 것도 개발자가 고려해야 할 내용일 것이다. 또, 컨텐츠를 중심으로 개발한다면 SSR 등 성능 처리에 따른 TPS 하락에 대한 대응도 쉽다. 단순하게 노출할 필요가 없는 컴포넌트를 제외하면 되기 때문이다. 개발자가 보는 데이터를 중심으로 개발한다면 그런 간단한 작업을 위해 리팩토링이 필요할지도 모를 일이다. 개발자로서 학습해왔다면 '중복과 복잡도를 줄이는' 것의 가치를 충분히 이해하고 있을테지만 가끔은 모두 내려놓자. 기획자나 디자이너의 의견을 묻자. 이 디자인이 바뀔 가능성이 있을까요?

 

 

백엔드에 종속되게 개발하자

프론트엔드는 백엔드의 API에 종속된다. 종속이란 단어에 자존심이 상한다면 '책임을 전가할 수 있다'로 이해했으면 좋겠다. 나는 항상 프론트엔드에서는 '어떻게 그려낼지'에 대해서 개발하고 백엔드에서는 '무엇을 그려낼지'에 대해 개발한다고 말한다. 이 말은 곧 장애의 책임은 백엔드라는 뜻이다. 어떻게 그려낼지에 대한 개발이 종료된 이상, 정해진 데이터에 대해서는 항상 같은 결과로 보여질 테니까.

 

실무를 진행하다 보면 데이터 처리를 프론트엔드에서 하는 코드를 자주 보게 된다. 개인적으로 이런 처리는 모두 백엔드에서 하는 것이 옳다고 본다. 그 이유는 크게 두가지다. 첫번째, API에서 객체 구조나 데이터의 형식을 바꾸는 것은 귀찮을지언정 어렵지는 않다. 애초에 그런 일을 수월하게 하기 위해 백엔드에 적합한 언어가 된 것이다. 흔히 사용하는 백엔드 언어, 그러니까 자바나 파이썬은 고차함수나 멀티쓰레딩을 통해 대규모 데이터 처리에 아주 강한 장점을 가지고 있다(자바에 대해서는 동의하기 어려울 수 있지만). 그런 장점을 가진 백엔드 API에서 반환할 JSON의 형식을 바꾸는 것은 정말 간단한 일이다. 두번째, API에서 데이터를 변경할 때 프론트엔드에 영향을 주기 때문이다. 이것이 가장 문제가 되는 내용인데, 프론트엔드에서 데이터 연산을 수행한다면 API 데이터가 변경되었을 때, 연산과 화면 구성 작업을 함께 해야 한다. 타입에 대해 자유로운 ECMA 특성상(타입스크립트라고 할 지라도) 신경써야 할 부분이 너무 많다. 처음부터 프론트엔드에서 사용할 데이터만을 뽑아 DTO로 작성하여 API에서 전달 해줬다면? API의 수정은 프론트엔드에 전혀 영향을 주지 않을 것이다.

 

내가 즐겨 사용하는 Vue.js에는 Dynamic Component라는 것이 있다. Component라는 컴포넌트에 is라는 특별한 attribute(directive)를 사용하는 것인데, 이 컴포넌트는 is로 전달받은 '컴포넌트 이름'에 해당하는 컴포넌트로 렌더링된다. 여기에 더해 v-bind directive로 컴포넌트에 파라미터 전체를 한번에 전달할 수 있다. 미리 대응하는 컴포넌트들을 등록해두면 상황에 맞게 컴포넌트가 변하는 것이다. 이렇게 '동적으로' 변하는 컴포넌트는 이 단락에서 이야기한 '백엔드에 종속적인 프론트엔드' 구성에 아주 유용하다. 백엔드에서 컴포넌트의 '이름'과 '데이터'를 전달함으로서 프론트엔드가 어떤 컴포넌트를 렌더링할지조차도 백엔드가 결정하게 하는 것이다.

 

백엔드에서 어떤 컴포넌트로 어떤 데이터를 그릴지까지 정하게 하면, 디자인 변경과 같은 '어떻게 그릴지'에 관한 작업이 아닌 이상 프론트엔드의 역할이 크게 줄어든다. 물론 이런 개발 환경을 완전하게 구성하기 위해서는 BFF 서버, 프록시(혹은 JSON)기반 데이터 개발 환경 등을 고려해야 하지만 이 단락에서 벗어난 내용이라 소개하지는 않는다.

 

왼쪽 두 이미지가 화면에 '무엇을 그릴지'에 해당하는 데이터다.

위 그림의 모바일 줌 닷컴이 이 단락에서 소개한 아키텍처를 적용한 프로젝트다(사내 정보가 아니라 클라이언트에서 호출하는 API다). 백엔드에서는 화면에 어떤 컴포넌트(data[].type)을 어떤 데이터로(data[].items) 그려낼지 전달한다. 상단의 탭 목록이나 이슈 검색어 뿐만 아니라 모바일 사이트의 컨텐츠 수직 정렬이라는 특성에 맞게 컴포넌트의 순서까지도 정해서 전송해준다. 만약 최상단의 텍스트 모듈을 이미지 모듈로 변경하고 싶다면 API에서 컴포넌트의 이름만 변경하면 될 것이다. 그런 '하찮은' 작업은 프론트엔드가 전혀 신경 쓸 필요가 없다.

 

이 프로젝트를 몇 년간 유지보수해 본 결과, 화면에 보여질 데이터와 화면을 그려낼 방법을 명확하게 분리해냄으로써 업무를 명확하게 나눌 수 있었고, 사이드 이펙트에서 매우 안전했다. 특히 포털 특유의 이슈에 대한 대응(특정 이슈가 발생했을 때 상단으로 이미지를 올리는 등)에 매우 적합하다는 것을 발견했다. 유지보수에 소요되는 리소스(인력과 시간 모두)가 크게 단축되어 여유로움이 생긴 것은 덤이다. 사이트의 모든 기능을 이런 식으로 구현하는 것에는 어려움이 있을 수 있지만 이런 식으로 백엔드에 '종속'된 프론트엔드를 고려할 필요가 있다.

 

 

통계를 고려하자

프론트엔드 개발에서 가장 고통스러운 것 중 하나가 통계다. 개인적으로 프론트엔드 개발에 있어 화면 개발이 7, 통계 개발 및 유지보수가 3정도 비율이라 생각한다. 논리적으로는 사용성을 개선하고 PV, UV를 높이기 위해서라는 것을 이해할 수 있지만 직접 추가하는 입장에서는 쓰잘데기 없는 작업으로 느껴진다. 때문에 통계를 모듈화, 공용화하여 최소한의 리소스만을 소모하도록 해야 한다. 이 통계를 위해 태어났다고 생각되는 것들을 활용하자. Vue.js의 Directive, React의 HOC등 말이다.

 

Vue.js의 Directive를 이용해 간단한 예시 코드를 작성했다. 디렉티브는 가상DOM에 부착하여 다양한 기능을 구현할 수 있게 한다. 예를 들어, 이 단락에서 소개하는 통계 작업의 경우 아래 이미지와 같은 형태로 구현할 수 있다.

 

통계 전송 디렉티브를 선언하고 전송할 값을 전달한다

굉장히 간단한 코드이지만 중요한 것은 코드가 아니다. 이 기능과 같은 통계 전송 코드를 프로젝트에 미리 만들어 두라는 것이다. 어차피 사용될 것이니 미리 만들어 두는 의미도 있지만 비어있는 값, 비어있는 컴포넌트를 작성해두는 바보같은 작업만으로도 전체적인 구조가 더욱 견고해진다. 이미 만들어진 아키텍처에 무엇인가를 더하는 것보다 이미 있는 것을 제외하는 것이 더 낫기 때문이다.

 

 

함수형 컴포넌트를 활용하자

함수형 컴포넌트, 그러니까 라이프 사이클과 컴포넌트 자체 데이터가 없는 컴포넌트는 Vue.js, React 모두에 존재하는 개념이다. 함수형 컴포넌트는 가볍다. 일반적인 컴포넌트가 VDOM-MVVM 객체로 짝을 맞춰 처리되는 것과 달리 VDOM만 존재하기 때문이다. 재미있는 점은 Vue.js의 경우에는 부모 컴포넌트에 붙어 부모 컴포넌트의 데이터를 사용할 수 있다는 점이다.

 

parent.$route를 통해 부모 컴포넌트의 $route 객체를 참조했다

위 이미지처럼 부모 컴포넌트의 객체를 참조할 수도 있다. 물론 부모 컴포넌트에 해당하는 값이 없을 수 있으니 사용처는 굉장히 제한된다.

 

소소한 팁으로 함수형 컴포넌트에 이벤트 핸들링도 가능하다

또, 함수형 컴포넌트를 활용하면 논리적인 구조를 나눌 수 있다. Atomic 디자인 패턴에 따라 설명하자면 분자(Molecules)쯤 되는 컴포넌트가 된다. 트리 형식으로 구조를 쉽게 설명할 수 있고, 재사용이 가능해진다. 특히 앞 단락에서 소개했던 내용들과도 일맥상통하는 부분이 있다. 첫번째, '컨텐츠 중심' 구성에 편리하다. 컨텐츠는 개발자가 데이터와는 차이가 있는데, 함수형 컴포넌트를 활용하면 컨텐츠에 맞는 컴포넌트 구성이 가능하다. 두번째로 '백엔드에 종속적인' 프론트엔드 구성에 유리하다. API에서 데이터를 명확하게 정의한 경우 연산이 줄어들어 함수형 컴포넌트의 비율이 커지기 때문이다. 함수형 컴포넌트는 다루기 까다롭지만, 어떻게 활용할지에 대해 고민해 볼 필요가 있다.