이번 약 한달간 AI 를 활용한 웹 소설 가상 독자 생성 프로젝트의 프론트엔드 개발자로 뒤늦게 참여하게 되면서 이번 3일간 디자인 시스템을 열심히 만들어봤다.
이번에 디자인 시스템을 만들며 이전에 만들었던 경험과 어떤 점을 발전 시켰고,느꼈던 생각들을 적어본다.
이전 프로젝트에서 디자인 시스템을 만들면서 느꼈던 어려움들
우선 운이 좋게 저번 프로젝트에서도 훌륭한 디자이너와 함께 작업하면서 디자인 시스템들을 만들어볼 기회가 생겼었다.
디자인 시스템을 만드는 경험은 아주 재밌던 경험이였지만 수 많은 어려움을 겪었었다.
만들어야했던 인풋 컴포넌트의 다양한 variant
디자인 시스템 자체는 다양한 상황을 커버해야 하기 때문에 추상화 수준이 높은 형태로 만들어져야만 했다.
처음 나는 외부에서 처리해야 할 상황들을 모두 props
들로 받고 props
들만 정해진다면 내부 로직이나 레이아웃부터 스타일까지 모두 결정된 jsx
자체를 반환하는 단단한 컴포넌트로 만들려했다.
export const Input = forwardRef<HTMLInputElement, InputProps>(
(
{
id,
componentType,
label,
essential = false,
isError = false,
disabled = false,
trailingNode,
leadingNode,
...rest
},
ref,
) => {
// input 컴포넌트의 상태에 따라 적용되는 스타일
...
...
// 실제 사용 예시
<Input
id="region-search"
componentType="searchText"
leadingNode={<SearchIcon />}
placeholder="동명(읍,면)으로 검색"
onChange={handleDebouncedChange}
ref={inputRef}
/>
이런식으로 시스템의 반환값 자체를 props
만으로 컨트롤 하려고 하니까 발생했던 문제들이 매우 많았다.
props
만으로 해당 컴포넌트가 어떤 값을 반환할지 한 눈에 이해하기 힘들었다.- 약속된 시스템 외에 다른 형태로 수정해서 사용하려 한다면
props
를 추가해야했다. - 모든 상황들을 변수인
props
로 처리하려 하다보니props
간 로직이 얽히고 섥혔었다. 얽히고 섥힌props
로직들은 유지보수하기 힘들뿐더러 확장하는데 어려움이 있었다.
개발 막바지엔 이런 문제점을 느끼고 리팩토링 하려했으나 결국은 그 얽히고 섥힌 로직들을 푸는데 실패했다.
이번에 시스템을 만들며 결심했던 점들
이번에 시스템을 만들며 가장 크게 결심했던 점은 디자인 시스템에 이용되는 컴포넌트들의 기능을 아주 최소화 하고 확장에 열려있도록 만드는 것 이였다.
그러기 위해선 컴포넌트 자체가 아주 가벼워아만 했고 가볍기 위해선 받는 props
들을 최소화 해야 했다.
컴포넌트를 가볍게 하기 위해 자연스럽게 다양한 상황들을 처리하는 곳이 컴포넌트 내부가 아닌 외부로 바꾸도록 했다.
그러면서 아주 자연스럽게 합성 컴포넌트 패턴을 이번에 많이 사용하게 되었다.
각 시스템의 컴포넌트들의 스타일은 불변할 요소들만 아주 최소한 정의해두고 레이아웃부터 크기까지 대부분의 스타일은 외부에서 주입하도록 하였다.
합성 컴포넌트 패턴을 활용한 시스템 개발
라디오 그룹 예시
예를 들어 라디오 인풋을 담당하는 컴포넌트는 이처럼 사용하도록 만들었다.
<RadioGroup
name="MultipleOptionsRadioStory"
value="multiple_option2"
className="flex flex-col gap-2"
onRadioGroupChange={action("라디오 그룹 값 변경")}>
<RadioGroup.Input value="multiple_option1" label="첫 번째 옵션">
<RadioGroup.Input value="multiple_option2" label="두 번째 옵션" />
<RadioGroup.Input value="multiple_option3" label="세 번째 옵션" />
</RadioGroup>
- 동일한
name
을 가진input type=radio
형태의 컴포넌트를 묶는다. - 각 인풋들의 변화를 외부에 알린다.
라디오 그룹이 해야 할 로직의 단위를 이정도로만 담당하고 어떻게 컴포넌트들을 배치 시킬지 , 어떤 형태로 배치 할지는 모두 실제 사용하는 곳에서 처리하도록 해줬다.
컴포넌트를 외부에서 자유롭게 배치할 수 있으면서도 약속된 하나의 시스템이란 것을 보장하기 위해 합성 컴포넌트 패턴은 필수적이였고
합성 컴포넌트 단에서 상태를 공유하기 위해선 React.Context
를 활용해주었다.
이를 통해 RadioGroup
내부 아이템들은 모두 따로 지정해주지 않아도 RadioGroup
에 설정된 item
을 공유하고 외부에서는 RadioGroup.Item
들 모두에 이벤트 핸들러를 달아줄 필요 없이 모든 값을 감싸고 있는 RadioGroup
에만 집중하면 됐다.
합성 컴포넌트 패턴을 이용하면 다양한 외부의 요구 사항에 대처 할 수 있다. 왜냐면 만들어둔 디자인 시스템들은 그저 하나의 jsx
형태이기에 원하는 컴포넌트를 사이에 넣거나 레이아웃들을 원하는데로 교체 할 수 있기 때문이다.
<TextField className="flex flex-col gap-5">
<div className="flex gap-5">
<TextField.Label htmlFor="pwd-input">비밀번호</TextField.Label>
<TextField.Input
id="pwd-input"
type="password"
aria-describedby="pwd-requirements"
placeholder="비밀번호를 입력하세요"
/>
</div>
<div id="pwd-requirements" className="text-secondary-200 text-sm">
비밀번호는 8자 이상, 특수문자 포함이 필요합니다.
</div>
</TextField>
원하는 컴포넌트가 있다면 단순히 마크업 언어에 불과한 디자인 시스템상의 컴포넌트를 이것 저것 조립하여 넣으면 되기 때문이다.
회고
대부분의 시스템을 합성 컴포넌트 패턴으로 만들며 무엇을 얻었나 생각해보면 수확이 꽤 많다.
- 외부 변경 사항에 유연하게 대처 할 수 있다.
- 각 컴포넌트들이 가볍기 때문에 테스트 하기 용이하다.
- 가독성 측면에서
jsx
와 크게 다른게 없어 한 눈에 보기 쉽다.
후하하 시스템은 끝났구 남은 3주간 달려가보자