react

[React] 리엑트 디자인 패턴: 컴포넌트와 합성 컴포넌트

tea-tea 2024. 3. 12. 09:36
컴포넌트란 무엇인가

컴포넌트 커스터마이징과 props 분기처리 문제

컴포넌트 설계 원칙(solid 원칙)

아토믹 디자인 시스템

합성 컴포넌트

 

컴포넌트란 무엇인가


정의

  • 추상적인 수준에서 컴포넌트는 독립적으로 작동가능한 모듈을 의미한다. 거친 예시로는 html의 각 태그이다. 이들은 각자가 독립적으로 사용가능하다. 
  • 정적인 웹이 대세를 이루던 시절(웹 1.0)에는 컴포넌트의 개념 대신, 페이지라는 개념이 주로 사용되었다.  페이지는 서버단에서 모든 데이터를 처리한 후에 전송되었고 추가적인 데이터를 받으려면 다시 페이지를 전송해야 했다. 하지만, ajax가 등장하고 웹 브라우저가 돌아가는 개인 pc의 성능이 좋아지면서 페이지의 주요 요소가 각자의 데이터 패칭과 비즈니스 로직으로 움직일 필요성이 생겨났다.(날씨 컴포넌트, 상품 리스트 컴포넌트, 장바구니 컴포넌트 등)
  • 즉, 컴포넌트는 비즈니스 로직을 컴포넌트 단위의 분담, 공통된 로직과 ui의 재사용, 느슨한 결합을 통한 유지보수의 맥락에서 대두된 개념

컴포넌트를 나누는 기준

추상적 수준에서 컴포넌트는 공통  관심사를 기준으로 분할한다. 

  • navigation 기능을 하는 컴포넌트
  • 웹사이트에서 현재 페이지의 계층을 보여주는 컴포넌트
  • 메인 아이템 리스트
  • 모달
  • float 버튼
  • ...

실무에서 가장 흔한 방식은 view(html 레이아웃+스타일링)를 기준으로 구분하는 것이다.

 

컴포넌트 커스터마이징과 props 분기처리 문제


문제 정의

웹사이트가 확장됨에 따라 개발자는 컴포넌트를 커스터마이징해달라는 요구를 처리해야 한다. 모달을 예시로 들자면,

  • 모달에 dimmed를 빼달라
  • 옵션 태그를 추가해달라
  • 버튼의 레이아웃을 변경해달라
  • 모달에 링크를 넣고  사용자가 클릭 시 로그인 계정의 권한에 따라 다르게 라우팅해달라.

이런 다양한 요구들이 발생한다. 이는 크게 세 가지 요구로 정리된다.

  • 요소의 추가 및 삭제
  • 요소의 레이아웃 변경
  • 스타일링 변경
  • 새로운 로직 추가

 

해결방안1: props 분기처리

가장 쉬운 방법은 props로 컴포넌트에 커스터마이징 요구사항을 전달하고 이에 따라 ui를 분기처리하는 것이다.

export function Modal({isOpen, addButton, submitButtonLayout, ...props, onClicklogic1}) {

return (
{!isOpen?null:  createPortal(<div className="modal"  ...props>
{submitButtonLayout="left"?<SubmitButton props="left"/>:<SubmitButton props="rigt"/>}
{!addButton?null:<button>add item</button>}
<button onClick={onClicklogic1}>click</button>
</div>,document.body) }
)


}

createPortal()

 

그러나 문제는..

  • 요소가 있는 경우와 없는 경우를 함께 return에 명시하면서 ui 가독성이 떨어짐
  • 레이아웃, 특히 위치를 바꾸는 건 분기처리로 구현하기 어려움
  • 커스텀으로 새로운 로직을 추가할수 있게 되면 재사용성의 문제
  • 많은 props를 하나의 컴포넌트를 사용할 때 모두 신경써야 함

 

그래서 개발자는 다른 방법을 모색할 필요가 있는데, 다음 사항에 주안점을 두어보자,

  • 범용 컴포넌트를 계층화하는 디자인 시스템
  • 페이지마다 범용 컴포넌트를 기반으로 커스텀 컴포넌트를 만들 방법

 

컴포넌트 설계 원칙(solid 원칙)


컴포넌트를 설계하는 데 있어서 유지보수를 위한 느슨한 결합은 중요하다. 이를 위해서 개발에 많이 적용되는 SOLID 원칙에 대해 알아보자.

 

단일 책임 원칙(SRP)

  • 컴포넌트는 하나의 비즈니스 로직만을 책임져야 한다. 컴포넌트의 경우, 복잡한 로직을 서로 다른 책임 관계로 분할할 필요가 있다.
  • 서버 패칭, 데이터의 필터링에 따른 렌더링
  • 필요하다면, 이를 커스텀 훅이나 유틸 함수로

 

개방 폐쇄 원칙(OCP)

  • 모듈은 변경에는 폐쇄적이고 확장에는 개방적이어야 함
  • 요구 사항에 대해 모듈 자체를 변경하는 식x, 확장으로 해결되는 구조
  • 컴포넌트 맥락에서는 child 컴포넌트 이용하면, 메인 컴포넌트의 내부를 바꾸지 않으며 해결 가능하다.
  • 이는 srp와도 맥락이 비슷한데, 합성 컴포넌트로 각 요구사항에 맞게 컴포넌트를 하위 컴포넌트로 분리
  • 카드 컴포넌트면, 썸네일, 카드바디(텍스트의 정렬 방식), 텍스트(텍스트 스타일링)
  • 부모 컴포넌트에서 커스터마이징 사항을 객체로 받은 후, props로 전달 전달하거나

 

리스코프 치환 원칙(LSP)

상위 객체의 기능은 하위 객체에서도 수행될 수 있어야 한다. 예컨대 조류와 참새는 날기, 쪼아 먹기 등의 기능을 동일하게 수행 가능하다. , 하위 객체가 상위 객체의 흐름, 컨셉을 상속한다는 것.

그러나, 이는 리엑트는 상속보단 합성 방식을 선호하므로 드물게 적용된다.

 

인터페이스 분리 원칙(ISP)

모듈의 인터페이스는 실제로 사용되는 최소한의 외부 의존성만을 가져야 한다. 컴포넌트의 맥락에서는 props로 의존성을 받을 때 실제로 사용하는 최소한의 값만을 받아야 한다.

 

아토믹 디자인 시스템


아토믹 디자인 시스템은 컴포넌트의 계층을 형성하고 이를 바탕으로 조합하는 방식이다. 

  • 컴포넌트의 단계를 atom, molecule, organism으로 나눈다.
  • atom | 가장 기본 단위의 html 요소로 button, input 등을 말하지만, 이 자체로는 특정한 목적을 가지고 있지 않다.
  • molcule | atom의 조합물로, 단일한 목적을 가지고 있다.(로그인 폼, 검색 바, 탭, 리스트)
  • organism | atom과 molcule의 조합물로, header, footer처럼 특정한 위치와 맥락에서 사용된다. 특정 맥락을 위한 컴포넌트이기 때문에, 재사용성이 낮다.
  • 워크 플로우 | 계획 단계에서는 페이지 내 모든 요소를 최대한 organism으로 환원한다. 이후, 각 페이지에서 공통되는 목적의 ui를 molcule로 환원하고 실질적으로 공통된 요소는 atom으로 분할한다.
  • 외부 의존성 | 재사용성을 위해서 범용 디자인이 아닌 부분들은 props로 주입받는다.
  •  

합성 컴포넌트


합성 컴포넌트는 하나의 컴포넌트를 여러 개의 하위 컴포넌트로 분할하여서 조합하는 패턴을 의미한다.(html의 select태그와 option 태그의 조합을 생각해보면 된다.)  이건 리엑트 개발자에게 이미 익숙한 개념이지만, 자유도가 높기 때문에 개발자의 주관에 따라 달라진다. 여기서 다루는 합성 컴포넌트는 아토믹 디자인 패턴 + 합성 컴포넌트의 방식으로 카카오 엔터테인먼트 기술 블로그에 나온 조합을 정리하였다.

 

  • 합성 컴포넌트는 container 역할을 하는 컴포넌트(메인 컴포넌트)와 내부의 서브 컴포넌트로 구성된다.
  • 메인 컴포넌트는 child를 이용해서 자식을 렌더링한다. 이는 ocp 원칙에 의해서 메인 컴포넌트를 변경할 필요성을 줄여준다.
  • props간에 복잡한 꼬임을 막기 위해서 context api를 사용한다.
  • 내부 요소 간의 위치 조정 문제는 서브 컴포넌트의 위치를 바꿈으로서 해결할 수 있다.
  • 긴 이름의 서브 컴포넌트들은 메인 컴포넌트 파일에서 props로 추가하면서 관심사를 분리할 수 있다.

 

 

참고자료

https://fe-developers.kakaoent.com/2022/220505-how-page-part-use-atomic-design-system/

https://fe-developers.kakaoent.com/2022/220731-composition-component/