Gloddy 프로젝트에서 8월 중순 쯤, 새로운 디자이너가 들어오며 디자인 시스템을 구축하였습니다. 이전에는 프론트엔드 개발자들이 공통적으로 사용되는 컴포넌트를 따로 빼서 공통 컴포넌트를 만들었습니다. 그러나 디자이너 분께서 친절하게 디자인 시스템을 만들어주셔서 이를 바탕으로 공통 컴포넌트를 만들어 나가고 있습니다. React앱에서 편리하게 사용할 수 있도록 라이브러리 차원에서 각 컴포넌트의 역할을 정의하고 실제로 사용하는 상황을 고려하여 여러 컴포넌트를 완성해나가고 있습니다.
프로젝트 초기에 구현한 컴포넌트는 input 및 textarea 태그 역할을 하는 TextField, button 태그 역할을 하는 Button처럼 비교적 단순한 컴포넌트들과 display:flex가 적용된 요소를 선언적으로 사용할 수 있는 Flex, 구획을 나눌 수 있는 Divider등의 Layout을 구현했습니다.

TextField 컴포넌트

Button 컴포넌트
개발이 점차 진행되면서 디자인 시스템에서 제공해야 하는 컴포넌트들의 복잡도도 올라가게 되었습니다. 가령, 아래 Carousel처럼 비교적 복잡도가 높은 컴포넌트는 어떻게 구현해야 할 지 고민하게 되었습니다.

이러한 컴포넌트들을 구현하기 위해서 선택한 방법인 Compound Component Pattern(합성 컴포넌트 패턴)에 대해서 알아보고, 이를 사용하여 Tabs 컴포넌트를 구현한 과정에 대해서 소개하겠습니다.
Compound Component Pattern을 소개하고 있는 수많은 글들 중 이 글에서는 이렇게 설명하고 있습니다.
Compound components are a React pattern that provides an expressive and flexible way for a parent component to communicate with its children, while expressively separating logic and UI.
Compound Component Pattern은 React 패턴 중 하나로, 여러 개의 작은 컴포넌트들이 각각의 역할을 분담하도록 하고 이를 조합하여 하나의 큰 컴포넌트를 만드는 것입니다. 부모 컴포넌트가 자식 컴포넌트와 분리된 로직과 분리된 사용자 인터페이스를 분리하면서 내부의 상태를 공유할 수 있는 패턴입니다.
합성 컴포넌트 패턴의 장점은 Context Provider를 가진 부모 컴포넌트로 여러 자식 컴포넌트를 감싸 props를 자식 컴포넌트에게 일일이 넘겨주지 않아도 된다는 점입니다. 또한, 개발자가 필요로 하는 자식 컴포넌트만 합성하여 사용할 수 있기 때문에 개발자에게 자율성을 줄 수 있다는 점입니다.
하지만, Context값이 변화하면서 Context값을 사용하는 컴포넌트에서 불필요한 리렌더링이 일어날 수 있고, JSX 소스 코드 길이가 길어질 수 있다는 단점이 있습니다.
따라서 합성 컴포넌트 패턴은 여러 컴포넌트가 함께 동작하고 내부적으로 상태와 로직을 공유할 때 사용할 것을 권장합니다. 예를 들어 select와 option요소가 이에 해당합니다. 이들은 '열림'과 '닫힘' 상태 등을 함께 공유하며, 독립적으로 사용될 수 없습니다. 아래에서 소개할 Tabs또한 동일합니다. Tabs의 상단부(Tab)와 하단부(Panel)은 '어떤 탭이 눌렸는지'에 대한 상태를 공유합니다. 이들도 독립적으로 사용될 수 없습니다.
Context API는 각 컴포넌트에 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있습니다. React 애플리케이션 안의 여러 컴포넌트들에 전해줘야 하는 상태값의 경우 Context API의 Provider로 감싸서, 이러한 값을 공유할 수 있습니다.