ComitChu는 GitHub 커밋 활동을 기반으로 펫이 진화하는 재미있는 웹 서비스다. 서비스가 점점 커지면서 공통 UI 요소를 효율적으로 관리하는 구조의 필요성을 절감했고, 이를 해결하기 위해 버튼과 입력창을 컴포넌트화(원자화) 하는 작업을 진행했다.
초기 코드베이스에는 다음과 같은 문제가 있었다:
index.css
에 혼재디자인 일관성과 유지보수성을 위해 공통 UI 컴포넌트 정리 및 원자화 작업이 필요했다.
UI 컴포넌트를 설계하면서 Brad Frost의 Atomic Design 개념을 도입했다. 이 방법론은 UI를 구성하는 요소들을 화학 구조처럼 작은 단위부터 큰 단위로 계층화하여 조직적으로 설계할 수 있도록 돕는다.
계층 | 설명 | ComitChu에서의 예시 |
---|---|---|
Atoms | 가장 기본적인 UI 요소, 더 이상 분해 불가 | Button , Input , Label , Icon 등 |
Molecules | 여러 원자가 결합한 작은 단위 기능 | 검색폼 = Input + Button , 유저정보 표시 = Avatar + Username |
Organisms | 여러 분자 및 원자가 모인 UI 블록 | Header = 로고 + 유저정보 + 로그아웃 버튼 |
Templates | Organism을 배치한 레이아웃 구조 | 대시보드 템플릿 레이아웃 |
Pages | 실제 콘텐츠가 채워진 화면 | /dashboard , /setting 등 페이지들 |
Atoms 단계에서 만든 Button
과 Input
컴포넌트는 이후 Molecule과 Organism 단계의 기반이 되며, 전체 UI의 일관성을 지키는 핵심 요소다.
src/
└─ components/
└─ common/
├─ Button.tsx
└─ Button.module.css
variant
prop을 통해 스타일 분기 처리 (예: primary
, danger
)Button.module.css
에 모듈화<button>
태그를 전부 Button
컴포넌트로 교체Button.tsx
코드import styles from "./Button.module.css";
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
children: React.ReactNode;
variant?: 'primary' | 'danger';
}
const Button: React.FC<ButtonProps> = ({
children,
variant = 'primary',
className,
...props
}) => {
const buttonClassName = `${styles.button} ${styles[variant]} ${className || ''}`;
return (
<button className={buttonClassName} {...props}>
{children}
</button>
);
};
export default Button;
Button.module.css
).button {
padding: 0.5rem 1rem;
border-radius: 6px;
font-weight: 600;
border: none;
cursor: pointer;
}
.primary {
background-color: #4a90e2;
color: white;
}
.danger {
background-color: #e74c3c;
}
.danger:hover {
background-color: #c0392b;
}
<Button>저장</Button>
<Button variant="danger">삭제</Button>
src/
└─ components/
└─ common/
├─ Input.tsx
└─ Input.module.css
Input
컴포넌트 생성Input.tsx
코드import styles from "./Input.module.css";
const Input = ({ className = "", ...props }: React.InputHTMLAttributes<HTMLInputElement>) => {
return <input className={`${styles.input} ${className}`} {...props} />;
};
export default Input;
.input {
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 1rem;
}
Header.tsx
에 있던 로그아웃 버튼에 variant="danger"
를 적용하여 스타일을 분리했다. 이로써 색상 커스터마이징이 단순해졌고, 로직과 스타일 간 결합도 낮아졌다.
<Button onClick={handleLogout} variant="danger">
Logout
</Button>
항목 | 개선 전 | 개선 후 |
---|---|---|
재사용성 | 낮음 | 높음 |
유지보수성 | 여러 파일 수정 필요 | 컴포넌트 단위 수정 가능 |
스타일 일관성 | 흐트러짐 | 공통 컴포넌트로 통일 |
확장성 | 낮음 | variant 등으로 분기 가능 |
UI의 기초 단위를 공통 컴포넌트로 정리하면서, 구조적 일관성과 유지보수성이 크게 향상되었다. 단순한 스타일링 분리처럼 보일 수 있지만, 실제로는 전체 프론트엔드 아키텍처의 기반을 정리하는 핵심 작업이었다. 프로젝트가 커질수록 이런 구조는 점점 더 큰 힘을 발휘하게 된다.