Intro
팀 프로젝트에서 디자인은 결과물의 퀄리티를 좌우하는 핵심 요소이다.
또한, 디자인 구현에 소요되는 작업 시간을 고려하지 않은 일방적인 디자인은 프로젝트 일정 관리에 차질을 줄 수 있기 때문에, 디자이너가 아니더라도 프론트엔드 팀은 결과물의 디자인에 대해 충분히 이해해야하고 깊이 관여해야 한다.
특히, 기획 초반 잘 만들어진 디자인과 디자인 컴포넌트는 총 7주 간의 프로젝트 일정에 큰 도움이 될 수 있음을 알고 있기에, 우리는 공통 컴포넌트를 도입하여 효율성을 높이고자 했다.
따라서 우리 프론트엔드 팀은 이러한 공통 컴포넌트의 중요성을 인식하고, 최적의 디자인과 재사용 가능한 컴포넌트를 설계하는 데 집중했다.
1. 공통 컴포넌트 디자인
1.1 목업을 통한 기초 작업

우리 팀은 확정된 제품의 디자인을 만들기 전에, 러프한 목업 작업부터 진행했다.
이는 단순히 디자인을 위한 것이 아니라, 전체 제품의 구조와 흐름을 파악하기 위해서였다.
1.2 반복적인 UI 식별

이후 제품의 디자인 컨셉을 결정하고, 피그마로 최종 목업을 완성했다.
목업을 바탕으로 유저 플로우를 검토하며 반복적으로 사용하는 UI들을 추려냈고, 이 과정에서 다음과 같은 질문들을 지속적으로 던졌다.
- 사용 빈도: 이 UI가 얼마나 자주 사용되는가?
- 대체 가능성: 기존의 UI로 대체가 불가능한가?
- 추상화: 이 UI를 더 단순화시킬 수 있는가?
- 사용 적합성: 사용자에게 이 UI가 적합한가?
1.3 최종 디자인 확정

식별된 UI 요소들 중에서, 우리는 사이즈와 색상을 최대한 단순화하여 공통 컴포넌트화할 수 있는 후보군을 선정했다.
그 결과, 우리가 개발해야할 공통 컴포넌트의 디자인이 결정됐다.
2. 공통 컴포넌트 설계
2.1 주요 고려 사항
만들어야 할 컴포넌트가 정해졌으면, 어떻게 구현할지에 대한 고민이 시작됐다.
우리의 주요 고민 사항은 다음과 같았다.
- 컴포넌트의 깊이:
- 너무 깊은 컴포넌트 구조는 이해와 유지보수를 어렵게 만든다
- 반면, 너무 얕은 구조는 재사용성이 떨어진다
- 따라서 각 컴포넌트는 단일 책임 원칙을 따르면서도, 불필요한 중첩을 피하는 균형 잡힌 컴포넌트여야 한다
- Props Drilling 방지:
- 여러번에 걸쳐 props를 전달해야 하는 상황은 코드의 가독성을 떨어뜨리고 유지보수를 어렵게 만든다
- Props Drilling이 필요하다면 전역에서 상태 관리를 하는 것이 낫다
- 유지보수를 고려한 재사용성:
- 향후 변경이나 확장에 유연하게 대응할 수 있어야 한다
2.2 구현 예시
/* Button.jsx */ import * as St from './Button.style'; const Button = ({ text, size, isRed = false, border = false, onClick }) => { return ( <St.StyledButton $size={size} $isRed={isRed} $border={border} onClick={onClick} > {size === 'small' ? ( <St.StyledTextP $isRed={isRed}>{text}</St.StyledTextP> ) : ( <St.StyledTextH4 $isRed={isRed}>{text}</St.StyledTextH4> )} </St.StyledButton> ); }; export default Button;
예시의 Button 컴포넌트를 보면 텍스트, 사이즈, 색상, 강조 유무, 테두리 유무, 클릭 이벤트 등을 Props로 받아서 유연하게 사용할 수 있도록 설계했다.
이러한 컴포넌트는 다양한 상황에서 재사용 가능하면서도, 필요에 따라 쉽게 커스터마이징이 가능하다는 장점이 있다.
2.3 복잡성 관리의 어려움
하지만 반드시 Button 컴포넌트와 같이 코드가 직관적인 컴포넌트만 있는 것은 아니다.
예를 들어, 우리의 InputBox 컴포넌트는 다양한 페이지와 상황에서 사용될 수 있도록 설계되었지만, 그 결과 많은 수의 props를 다루게 되었다.
import { useState } from 'react'; import AlertMessage from '@/components/_common/AlertMessage.jsx'; import { icons } from '@/shared/constants/icons'; import * as St from './InputBox.style'; const InputBox = ({ labelText, // 라벨 이름 iconName, // 사용 아이콘 이름, 없으면 'empty' inputType, size, // 작은건 small 아님 100% 맞춰짐 autocomplete, onChange, name, // 전달받은 입력 필드 이름 value, // 해당 입력 필드에 입력된 값 alertContents, // 에러메세지 내용 readOnly, isProfileViewPage, onClickInputBox, }) => { const [isPasswordVisible, setIsPasswordVisible] = useState(false); //const iconSrc = icons[iconName]; const iconSrc = inputType === 'password' ? isPasswordVisible ? icons.visible : icons.invisible : icons[iconName] || icons.empty; // 가시성 설정 토글 const handleToggleVisibility = (event) => { event.preventDefault(); setIsPasswordVisible(!isPasswordVisible); }; // password만 분기 적용 const currentInputType = inputType === 'password' && isPasswordVisible ? 'text' : inputType; const handleKeyDown = (e) => { if (e.key === ' ') { // 띄어쓰기를 차단 e.preventDefault(); } }; return ( <St.InputBoxContainerWithAlertMessage> <St.InputBoxContainer $size={size} $isProfileViewPage={isProfileViewPage} onClick={onClickInputBox} > <St.TextContainer> <St.InputLabel>{labelText}</St.InputLabel> <St.Input type={currentInputType} autoComplete={autocomplete} value={value} onChange={onChange} name={name} readOnly={readOnly} $isProfileViewPage={isProfileViewPage} onKeyDown={handleKeyDown} /> </St.TextContainer> {inputType === 'password' ? ( <St.InputIcon as="button" // 버튼처럼 사용 onClick={handleToggleVisibility} > <St.InputIcon src={iconSrc} alt={isPasswordVisible ? 'Hide password' : 'Show password'} /> </St.InputIcon> ) : ( <St.InputIcon src={iconSrc} $isEmpty={iconSrc === 'empty'} /> )} </St.InputBoxContainer> <AlertMessage message={alertContents} size={'small'} /> </St.InputBoxContainerWithAlertMessage> ); }; export default InputBox;
이 InputBox 컴포넌트는 회원가입, 마이페이지, 모달 등 다양한 페이지에 적용될 수 있도록 총 12개의 Props를 사용했다.
이는 컴포넌트의 재사용성을 극대화했지만, 동시에 로직 관리의 복잡성을 증가시키는 결과를 가져왔다.
3. 공통 컴포넌트의 장단점
공통 컴포넌트를 프로젝트에 도입한 결과, 다음과 같은 장단점을 경험했다.
장점
- 일관된 디자인 유지: 모든 UI가 일관된 스타일을 유지하게 되어 사용자 경험이 향상되었다
- UI 구현 시간 단축: 기존 컴포넌트를 재사용할 수 있어 UI 구현 시간이 크게 줄어들었다
- 높은 유지보수성: 변경 사항이 발생해도 공통 컴포넌트만 수정하면 전체 프로젝트에 반영할 수 있어 유지보수가 수월해졌다
- 협업 효율성 향상: 팀원들이 동일한 컴포넌트를 사용함으로써 서로의 코드를 쉽게 이해하고 협업할 수 있었다
단점
- Props Drilling: Props가 지나치게 많아지면서 로직 관리가 어려워지는 경우가 있었다. 이를 해결하기 위해 컴포넌트 설계를 더 세밀하게 조정할 필요가 있었다.
- 초기 작업 시간 증가: 컴포넌트 설계와 초기 구현에 시간이 많이 소요되었다. 특히 디자인과 개발 초기에는 많은 리소스가 투입되었다.
4. 마무리
우리 팀은 MUI나 Tailwind 같은 잘 만들어진 UI 라이브러리를 사용하는 대신, 깊이 있는 학습을 위해 직접 공통 컴포넌트를 개발해서 적용했다.
기존에 잘 만들어진 라이브러리들은 복잡한 디자인 시스템과 재사용성을 이미 잘 고려한 상태에서 제공되지만, 이를 직접 구현해보며 이게 왜 편리한지를 아는 것이 중요하다고 생각했다.
회고하는 과정에서 공통 컴포넌트에 대해 찾아보며 우리는 단순히 재사용성에만 매몰되어 공통 컴포넌트를 바라보았다는 점을 알게되었다.
공통 컴포넌트는 단순히 재사용 가능한 요소 그 이상이었다.
- 공통 컴포넌트는 확장성을 고려해 설계해야 하며, 웹 접근성을 포함한 다양한 사용자 요구사항을 충족할 수 있어야 한다.
- 또한, SSR, 모노레포, Github Packages 같은 최신 기술을 활용해 컴포넌트의 관리와 배포를 효율적으로 처리하는 환경도 중요하다.
따라서 앞으로는 기술적인 깊이와 사용자 경험을 모두 고려하여 공통 컴포넌트를 설계하고, 더 나은 웹 접근성과 확장성을 보장하는 방향으로 적용할 수 있도록 해야겠다.