컴포넌트 합성 : 서브 컴포넌트의 모든 것
원문 : https://medium.com/eightshapes-llc/subcomponents-753ce9f6600a
TL;DR
재사용성을 고려한 컴포넌트를 만들기 위해서...
- 모든 것의 제어를 포기하고 (inversion of control)
- 부픔(서브컴포넌트)를 제공하고
- 구현하는 개발자가 컴포넌트를 합성하도록 합니다
디자인 시스템의 모든 사용 사례를 컴포넌트화 하는 것은 어리석은 일입니다만,
실무에서는 기존 피그마 컴포넌트를 작게 분리하는 것을 싫어하는 경향이 있습니다.
그리고 개발자는 바닥부터 컴포넌트를 다시 만드는 것을 싫어하죠.
컴포넌트 라이브러리가 점점 원자와 분자로 채워짐에 따라, 거대한 설정 덩어리가 생겨나며,
이러한 복잡성은 다음과 같은 디자인 결정을 강요합니다.
- 특정 컴포넌트는 설정의 일부 항목을 지원하지 않기로 합니다.
- 그럼에도 불구하고 모든 설정을 최대한 지원하도록 노력합니다.
이 게시물에서는
설정 기반 컴포넌트의 한계를 알아보고,
설정 기반 프로그래밍의 대안으로서,
재사용 가능한 청크와 유연한 컨테이너를 분리하여 합성하는 방법을 알아봅니다.
설정 기반 컴포넌트
아래의 카드 컴포넌트는 다음과 같은 설정(props)과 슬롯(slot)이 존재합니다.
- 컴포넌트 합성을 위한 슬롯(slot)
- 시각적 표현을 위한 속성 (prop)
- 콘텐츠를 위한 속성 (prop)
다양한 속성들을 통해 아래와 같이 여러 카드의 변형을 만들 수 있습니다.
하지만 prop 기반 설정 방식으론 아래의 어떤 것도 구현할 수 없습니다.
- Card 수직 방향 항목 순서 바꾸기
- Card 수평 방향 항목 순서 바꾸기
- Card 구석에 삭제 버튼 추가하기
- 타이틀 앞에 뱃지 추가하기
- 이미지 대신 배경 아이콘 보여주기
- 액션 버튼, 링크 버튼 대신 아이콘 버튼 사용하기
많은 팀에 서비스를 제공하는 시스템의 경우 이러한 케이스들이 앞으로 필요할 수 있습니다.
이런 경우가 등장할 때마다 새로운 디자인, 컴포넌트가 필요할까요?
설정에서 합성으로
우리는 다음과 같은 방법으로 컴포넌트의 사용 사례를 추가할 수 있습니다.
옵션 1: 설정 추가하기
엘리먼트가 필요하면 엘리먼트를 추가합니다.
prop이 필요하면 prop을 추가합니다.
디자이너와 개발자는 컴포넌트의 API(craft a component’s API)를 만들기 위해 유사한 접근 방식을 사용합니다.
이 습관은 자연스럽고 예측 가능합니다. 필요한 기능을 하나씩 추가하기만 하면 됩니다.
이 방법의 단점은 다음과 같습니다.
- 기능이 너무 많은 컴포넌트는 변경에 취약해집니다.
- prop이 너무 많으면, 컴포넌트를 이해하기 어려워집니다.
- 모든 prop 집합이 앞으로의 모든 사용 사례를 커버하는 건 아니며, 때로는 불필요한 속성이 존재할 수도 있습니다.
- 테스트도 점점 어려워집니다.
죄송하지만, 이 방법은 사양하겠습니다.
옵션 2: 개발자가 처음부터 만들도록 하기
디자인 시스템에 없으면 개발자가 직접 만들도록 합니다.
이 방법은 문제가 뭘까요?
- 시스템이 없어 일관적이지 않습니다.
- 디자인에 시간과 비용을 많이 씁니다.
- 스타일, 구조, 상태 등이 기존 시스템과 동기화되지 않습니다.
- 색상, 타이포그래피, 공간, 크기 등...
모든 측면에서 시스템 차원에서 지원해야하는 변형이 부족합니다.
또한 아무도 처음부터 계속 컴포넌트를 만들고 싶어하지 않습니다.
옵션 3: 서브 컴포넌트를 제공하기
아래의 조건을 충족하는 작지만 유용한 부품을 제공할 수 있다면 어떨까요?
- 필요한 모든 요구 사항을 충족합니다.
- 디자인 시스템과 일관성을 유지합니다.
- 시스템 개발을 가속화합니다.
서브컴포넌트:
특정 상위 컴포넌트 또는 컨텍스트 내에서만 사용하며,
이를 의도한 잘 정의된 API를 지닌,
독립적으로 합성 가능한 UI 컴포넌트 입니다.
Card는 다음과 같이 쪼갤 수 있습니다.
- CardContainer
- CardText
- CardImage
- CardActions
List는 다음과 같이 쪼갤 수 있습니다.
- ListItemText
- ListItemIcon
- ListItemAction
서브컴포넌트는 범위와 의도를 표현하기 위해
상위 이름(List 또는 Card) 앞에 추가되는 이름(ListItem 또는 CardText)을 사용합니다.
서브컴포넌트는 다음과 같은 많은 이점을 제공합니다.
- 더 작은 prop 집합으로 더 많은 케이스를 유연하게 지원합니다.
- 개발자가 시스템에 얽매이지 않고 더 많은 것을 제어할 수 있도록 권한을 부여합니다.
- 개발자가 고유한 요구사항을 해결하기 위해 더 많은 자유도를 얻을 수 있으므로 디자인 시스템 팀에 대한 의존도가 감소합니다.
- 많은 팀에서 공유할 필요가 없는, 시스템을 복잡하게 만드는 기능 추가를 줄입니다.
- 개발자가 도전하고, 혁신하고, 확장할 수 있느 여러 방법에 대한 탐색과 다양한 사고를 장려합니다.
서브 컴포넌트 구현하기
- 재사용 가능한 청크와 이를 배치할 컨테이너를 식별합니다.
- prop들을 컨테이너 단위로 분산합니다.
1. 재사용 가능한 청크 식별하기
독립적으로 배치할 수 있지만 더 이상 나눌 필요가 없는 요소를 식별하기 위해, 작은 요소부터 시작합니다.
서브컴포넌트는 목적에 따라 다음과 같이 분류할 수 있습니다.
- 잠금(lockups)
- 확장(extensions)
- 조합(combinations)
- 반복되는 요소 집합(repeating sets of elements)
잠금(lockups)
서브컴포넌트 잠금은 요소를 결합하고 내부 공간을 캡슐화합니다.
예를 들어 CardText는 title과 metadata, description을 결합합니다.
prop에는 colorMode(밝음/어두움용), size 및 inset 토글(인접한 항목과 쉽게 합성할 수 있도록)이 포함될 수 있습니다.
확장(extension)
- alt-text prop을 전달
- 호버 애니메이션을 추가
- asprct-ratio 변형을 Card 컴포넌트와 관련된 변형으로만 제한하는 파사드
- 특정 프롭을 금지
조합(combinations)
- 버튼, 링크 및 아이콘 버튼의 수량
- 버튼, 링크 및 아이콘 버튼의 조합
- 버튼, 링크 및 아이콘 버튼의 순서
- 버튼, 링크 및 아이콘 버튼의 간격
- 버튼, 링크 및 아이콘 버튼의 변형
- API의 이해를 돕습니다.
- (권장하는 옵션을 가시화)
- 일관성을 개선합니다.
- (옵션을 제한적인 집합으로 제한)
- 효율성을 증가시킵니다.
- (설정하기 더 쉬운 API)
조합은 설계 도구와 코드 도구에서 서로 다른 접근 방식을 보장할 수 있습니다.
- 디자이너는 컴포넌트들의 권장되는 조합, 크기 및 좋은 디폴트 값을 중요하게 생각할 수 있습니다.
- 개발자는 보다 일반적인 컨테이너를 이용한 유연한 합성을 선호할 수 있습니다.
반복(repeater)
- CheckboxGroup 내의 CheckboxItems, List 내의 ListItems 또는 Stepper 내의 Steps와 같은 리스트(그룹)를 형성합니다.
- 각 항목은 그 안의 요소를 앞뒤 항목과 관련하여 정렬합니다.
2. 컨테이너 식별하기
- 레이아웃 규칙을 캡슐화합니다.
- 간격을 제어합니다.
- 배경색, 둥근 모양 및 대화형 상태와 같은 prop을 적용할 수 있습니다.
CardContainer와 같은 일반적인 컨테이너(general container)는 좋은 출발점입니다.
각 서브컴포넌트는 유연한 합성의 대상이지만,
각 타입 지정 컨테이너 내에는 레이아웃과 간격이 존재합니다.
CardMedia 타입 컨테이너는 SpotIllustration, Image, ProductImage 또는 Icon 중 하나를 서브 컴포넌트로 예상합니다.
반면 CardImage 컨테이너는 Image만 서브컴포넌트로 예상합니다.
저는 Card의 서브컨테이너를 설계할 때 다음과 같은 질문에 직면했습니다
- 상태(focus, hover 등)를 최상위 CardContainer에 결합할까요?
- 별도의 CardButton 하위 컴포넌트를 제공할까요?
- colorMode를 제한된 집합으로 제공할까요?, 아니면 16진수나 모든 색상 토큰을 받을 수 있도록 할까요?
- 패딩을 강제할까요? 패딩을 많이 혹은 적게 제공해야할까요? 디폴트로 패딩을 제공하지 않고, inset prop을 제공할까요?
- Card 콘텐츠를 어떻게 처리할까요?
- CardContent 서브컴포넌트를 포함하고 해당 컴포넌트에 패딩을 추가하면, spacing 원칙을 따르지만 복잡성을 추가합니다.
- CardText 컴포넌트에 마진을 추가하는 것은 효율적이지만, 마진을 이용해 공간을 만드는 것은 spacing 원칭을 위반하는 것입니다.
주 : 개발 시 컴포넌트의 명시적 spacing 관심사를 없애려면 최대한 중첩 합성하는 방법밖엔 없더라구요 ㅎ
서브컴포넌트 합성
위에서 설명한 설정으로 불가능했던 사용 사례들을 구현해 봅니다.
순서 변경
컨테이너 내에서 CardText와 CardImage의 순서를 변경합니다.
레이아웃 변경
카드컨테이너의 클래스명 혹은 속성을 통해 레이아웃을 변경합니다.
요소 추가하기
스타일 커스터마이징
아이콘과 배경색이 있는 미디어 영역을 원한다면
CardImage 대신 CardMedia를 사용합니다.
중첩 컴포넌트 포함하기
대화형 요소 포함하기
위와 같이 각 예시에서 합성은 매우 간단함을 알 수 있습니다.
서브 컴포넌트에 익숙해지기
- 매우 일반적인 컴포넌트를 설정 기반으로 만듭니다.
- 일반적이지 않은 컴포넌트는 합성 기반으로 만듭니다.
- 덜 일반적인 컴포넌트는 시간이 허락하는 한 설정 기반으로 만듭니다.
서브 컴포넌트를 분리 및 설계하는 것은 어렵지만,
개발, 테스트, 유지보수의 효율성을 높여줍니다.
내용의 핵심은 좋은데 원 저자가 글을 못알아먹게 써서 요점을 파악하는게 어렵군요
같이 보면 좋은 글
https://itchallenger.tistory.com/905