[7팀 학습메이트] Chapter3-1. UI 컴포넌트 모듈화와 디자인 시스템 #51
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Chapter3-1. UI 컴포넌트 모듈화와 디자인 시스템
과제 목표
레거시 코드베이스를 현대적인 디자인 시스템으로 개편하는 실무 경험
Before 패키지 분석 후 After 패키지 개편
개편 목표
디자인 시스템
컴포넌트 아키텍처
사용할 도구
TailwindCSS 4.x
shadcn/ui
CVA (Class Variance Authority)
React Hook Form + Zod
필수 과제
1. 디자인 시스템 구축
2. Before 패키지 분석
3. 컴포넌트 개편
심화 과제
과제 회고
Before 패키지에서 발견한 문제점
스타일링 측면의 문제점
1. 스타일링 방식 통일성 X
Before 패키지에서는 인라인 스타일과 CSS 클래스가 혼재하여 사용되고 있습니다. 예를 들면
이러한 방식은 코드의 일관성이 떨어져 유지보수가 어려워집니다. 또한 특정 스타일이 어디에 정의되어 있는지 찾기 어렵고, 스타일 변경 시 여러 곳을 수정해야 하는 불편함이 있습니다. 인라인 스타일은 재사용성이 낮아 동일한 스타일을 반복적으로 작성해야 하는 문제가 있습니다.
2. 하드코딩되어 있는 디자인값들
색상, 간격, 타이포그래피 등의 디자인 값들이 일관성 없이 하드코딩되어 있습니다.
이런 방식은 디자인 시스템의 일관성을 유지하거나 유지보수하기 어려운 문제가 있습니다. 예를 들어, 동일한 primary 색상이 여러 곳에서 다른 값으로 하드코딩되어 있을 경우, 디자인 변경 시 모든 곳을 찾아 수정해야 합니다. 또한 테마 변경이나 다크 모드 기능을 추가할 때도 한곳에서 관리할 수 있는 디자인 토큰이 없으면 시스템 전체의 색상 팔레트를 일괄적으로 변경하기 어렵습니다.
더 나아가, 새로운 디자이너나 개발자가 프로젝트에 합류했을 때 어떤 색상이나 간격 값을 사용해야 할지 명확한 가이드가 없어 혼란이 발생할 수 있습니다.
컴포넌트 설계 측면의 문제점
1. UI 컴포넌트에 도메인 로직 혼재
Before 패키지에서는 UI 컴포넌트에 비즈니스 도메인 로직이 직접 포함되어 있습니다. 예를 들어, Table 컴포넌트는 'user'와 'post'라는 특정 도메인 타입을 알고 있으며, FormInput은 비즈니스 규칙(예약어 체크, 이메일 도메인 검증)을 직접 검증합니다
이러한 설계는 컴포넌트의 재사용성을 크게 저해합니다. Table 컴포넌트를 다른 도메인에서 사용하려면 컴포넌트 자체를 수정해야 하며, FormInput을 다른 검증 규칙이 필요한 곳에서 사용하기 어렵습니다. 또한 UI 컴포넌트와 비즈니스 로직의 강한 결합으로 인해 단위 테스트 작성이 어렵고, 비즈니스 규칙 변경 시 UI 컴포넌트까지 함께 수정해야 하는 문제가 발생합니다.
2. 일관성 없는 컴포넌트 API
비슷한 목적의 prop에 대해 컴포넌트마다 다른 네이밍을 사용하고 있습니다.
개발자가 컴포넌트를 사용할 때마다 어떤 prop 이름과 값을 사용해야 할지 매번 확인해야 하며, 실수로 잘못된 prop를 사용할 가능성이 높습니다. 또한 디자인 시스템의 일관성 부분에서도 문제가 됩니다. 컴포넌트 간 일관된 API가 없으면, 전체 시스템이 하나의 통합된 디자인 시스템으로 느껴지지 않습니다.
3. 타입 안전성 부족
프로젝트 전반에 걸쳐
any타입이 광범위하게 사용되고 있습니다타입 안전성이 부족하면 런타임 에러 발생 가능성이 높아집니다. TypeScript의 컴파일 타임 체크를 활용할 수 없어, 잘못된 프로퍼티 접근이나 타입 불일치로 인한 오류를 사전에 발견하기 어렵습니다. 또한 IDE의 자동완성과 리팩토링 지원을 제대로 활용할 수 없어 개발 생산성이 저하됩니다. 더 나아가, 코드의 의도를 파악하기 어려워 유지보수가 어렵고, 팀 협업 시 예상치 못한 버그가 발생할 가능성이 높아집니다.
폼 관리 측면의 문제점
1. 폼 상태 관리의 복잡성
폼 데이터가
any타입으로 관리되고 있으며, 폼 필드 렌더링 코드가 중복되어 있습니다이러한 방식은 타입 안전성을 보장할 수 없을 뿐만 아니라, 폼의 구조를 파악하기 어렵습니다. 새로운 필드를 추가하거나 기존 필드를 수정할 때, 타입 정의가 없어 어떤 필드가 필수인지, 어떤 타입의 값이 들어가야 하는지 명확하지 않습니다. 또한 중복된 코드는 폼 구조 변경 시 여러 곳을 동시에 수정해야 하는 부담을 줍니다.
2. UI 컴포넌트와 validation 로직 내부에 있음
validation 로직이 UI 컴포넌트인 FormInput 컴포넌트 내부에 포함되어 있습니다.
이러한 validation 로직이 분산되어 있으면, 동일한 validation 규칙이 여러 곳에 중복 구현될 가능성이 높습니다. 예를 들어, 이메일 도메인 검증 로직이 여러 FormInput 인스턴스에 각각 구현되어 있다면, 검증 규칙 변경 시 모든 곳을 찾아 수정해야 합니다. 또한 검증 로직을 독립적으로 테스트하기 어려워, 비즈니스 규칙의 정확성을 보장하기 어렵습니다. 더 나아가, UI 컴포넌트와 검증 로직의 강한 결합으로 인해 컴포넌트의 재사용성이 떨어지고, 다른 도메인에서 동일한 UI 컴포넌트를 사용하기 어려워집니다.
개편 과정에서 집중한 부분
개편 과정에서는 크게 세 가지 영역에 집중하여 개선 작업을 진행했습니다.
1. UI 컴포넌트와 비즈니스 로직의 명확한 분리
가장 우선적으로 집중한 부분은 UI 컴포넌트에서 비즈니스 로직을 완전히 분리하는 것이었습니다. Before 패키지에서는 Table, Button, FormInput 등의 컴포넌트가 도메인 타입이나 비즈니스 규칙을 직접 알고 있어 재사용성이 떨어졌습니다. 이를 개선하기 위해, 모든 UI 컴포넌트는 순수하게 뷰만 담당하도록 리팩토링했습니다.
예를 들어, FormInput 컴포넌트는 검증 로직을 제거하고 단순히 에러 메시지를 표시하는 역할만 하도록 변경했습니다. 검증 로직은
useFieldValidation이라는 커스텀 훅으로 분리하여, 페이지 레벨에서 필요한 비즈니스 규칙에 맞게 검증을 수행하도록 구성했습니다. 이를 통해 FormInput 컴포넌트는 어떤 도메인에서도 재사용 가능한 순수 UI 컴포넌트가 되었습니다.2. 디자인 토큰 기반의 일관된 스타일 시스템 구축
Before 패키지의 하드코딩된 스타일 값들을 디자인 토큰으로 체계화하는 데 집중했습니다.
src/styles/tokens.ts파일에 색상, 간격, 타이포그래피, 그림자 등을 정의하고, 이를 TailwindCSS 설정과 CSS 변수로 연결하여 프로젝트 전반에 걸쳐 일관되게 사용할 수 있도록 구성했습니다.특히, 기존 코드에서 실제로 사용되는 값들만 추출하여 토큰으로 정의함으로써 불필요한 추상화를 피하고 실용적인 디자인 시스템을 만들었습니다. 이를 통해 디자인 변경이 필요할 때
tokens.ts파일만 수정하면 전체 시스템에 일괄적으로 반영되도록 개선했습니다.3. 사용자 경험 개선을 위한 Form Validation UX 향상
Form validation의 사용자 경험 개선에도 집중했습니다. Before 패키지에서는 모달이 열리자마자 validation 에러 메시지가 표시되어 사용자에게 불필요한 부담을 주었습니다. 이를 개선하기 위해
touched상태를 도입하여, 사용자가 실제로 필드에 입력을 시작한 후에만 validation 메시지가 표시되도록 구현했습니다.또한 모달이 닫힐 때 모든 validation 상태를 초기화하여, 다시 모달을 열었을 때 이전 입력에 대한 에러 메시지가 남아있지 않도록 처리했습니다. 이러한 변경으로 사용자가 폼을 처음 볼 때는 깔끔한 상태를 유지하고, 입력을 시작한 후에만 피드백을 받을 수 있게 하였습니다.
사용한 기술 스택 경험
TailwindCSS
TailwindCSS를 활용한 유틸리티 우선 접근법을 경험했습니다. 특히 디자인 토큰을 TailwindCSS의 theme 설정에 연결하여, 디자인 시스템의 값들을 유틸리티 클래스로 자연스럽게 사용할 수 있게 구성했습니다.
처음에는 TailwindCSS v4를 사용하려고 했으나, fractional spacing 클래스(
py-1.5,py-2.5)가 제대로 생성되지 않는 이슈가 발생하여 v3로 다운그레이드했습니다. 이를 통해 안정성과 기능의 균형을 맞추는 것이 중요하다는 것을 배웠습니다.shadcn/ui
shadcn/ui의 컴포넌트 설계 철학을 경험했습니다. shadcn/ui는 라이브러리가 아닌 소스코드로 제공되기 때문에, 설치한 컴포넌트를 직접 수정하고 커스터마이징할 수 있다는 점이 인상적이었습니다.
Card, Alert 등의 컴포넌트를 shadcn/ui 기반으로 리팩토링하면서, 기존 API와 스타일을 유지하면서도 내부적으로는 접근성과 타입 안전성이 보장되는 구조로 개선할 수 있었습니다. 특히 Radix UI 기반의 컴포넌트들이 접근성 속성을 내장하고 있어, 별도의 접근성 작업 없이도 기본적인 접근성을 보장받을 수 있다는 점이 큰 장점이었습니다.
CVA (Class Variance Authority)
CVA를 활용하여 선언적인 variants 패턴을 구현했습니다. Button과 Badge 컴포넌트에서 CVA를 사용하여 variant와 size를 타입 안전하게 관리할 수 있었습니다. 특히 variants를 객체 형태로 선언함으로써, 컴포넌트의 다양한 조합을 명확하게 정의하고 TypeScript의 타입 추론을 활용할 수 있었습니다.
Storybook
Storybook을 설정하고 모든 컴포넌트에 대해 stories를 작성했습니다. Storybook을 통해 컴포넌트를 격리된 환경에서 개발하고 테스트할 수 있었으며, 디자인 토큰을 시각적으로 확인할 수 있는 페이지도 구성했습니다.
초기 설정 과정에서 버전 호환성 문제나 설정 이슈가 있었지만, 이를 해결하면서 Storybook의 설정 방식과 워크플로우에 대해 깊이 이해할 수 있었습니다. 또한 모든 story 파일을
src/stories/폴더로 통합하여 관리함으로써, 컴포넌트 문서화하려 하였습니다.어려웠던 점과 해결 방법
1. TailwindCSS v4의 fractional spacing 클래스 생성 문제
TailwindCSS v4를 처음 사용했을 때,
py-1.5,py-2.5같은 fractional spacing 클래스가 제대로 생성되지 않는 문제가 발생했습니다. safelist에 명시적으로 추가해도 해결되지 않았고, Vite와 PostCSS 설정을 여러 번 수정해봐도 동일한 문제가 지속되었습니다.이 문제를 해결하기 위해 TailwindCSS를 v3.4.18로 다운그레이드했습니다. v3에서는 fractional spacing 클래스가 정상적으로 생성되었고, 프로젝트에서 필요한 모든 유틸리티 클래스를 안정적으로 사용할 수 있게 되었습니다. 이를 통해 최신 버전이 항상 최선은 아니며, 프로젝트의 안정성과 기능 요구사항을 고려하여 적절한 버전을 선택하는 것이 중요하다는 것을 배웠습니다.
2. Storybook 설정의 복잡성
Storybook v10의 초기 설정 과정에서 여러 이슈가 발생했습니다. 특히 addon 버전 호환성 문제와 Vite 설정 상속 문제로 인해 처음에는 제대로 작동하지 않았습니다.
문제를 해결하기 위해 Storybook의 공식 문서를 참고하여 최신 버전으로 재설치했고, viteFinal 설정에서 path alias와 PostCSS 설정을 명시적으로 구성했습니다. 또한 Storybook이 자동으로 postcss.config.js를 읽도록 하면서도, 명시적으로 설정을 확인할 수 있도록 구성했습니다. 이러한 과정을 통해 Storybook의 설정 구조와 Vite 통합 방식에 대해 깊이 이해할 수 있었습니다.
아쉬운 점과 향후 개선이 필요한 부분
과제가 너무 재밌어 보이기도 했고, 팀원들과 새로운 과제에 대해 이야기해보고 싶은 마음에 시작하였습니다! 하지만 늦게 시작한 탓에 목표한만큼 시간안에 완성하지 못해 아쉬움이 남습니다.
처음 과제를 시작할때는 리팩토링보다 디자인 시스템의 기반을 만드는 것에 집중했습니다. TailwindCSS 설정, 디자인 토큰 구성, Storybook 환경 설정 등 전체 구조를 잡는 데 많은 시간을 투입하다 보니, 컴포넌트 리팩토링을 제대로 시작하지 못했습니다. Button, Badge, Form 등의 주요 컴포넌트는 Tailwind 기반으로 개선했지만, 레거시 CSS가 일부 남아 있고 하드코딩된 색상값을 디자인 토큰으로 완전히 치환하지 못한 부분도 아쉬운 점으로 남습니다.
레거시CSS들을 TailwindCSS로 마이그레이션이 완료되면 FSD와 유사한 구조로 리팩토링하고 싶었지만, Atomic 구조와 shadcn/ui의 단일 구조가 혼재된 상태에서 원하는 만큼 정리하지 못했습니다. Modal과 Table 같은 컴포넌트도 shadcn/ui 기반으로 완전히 리팩토링하지 못해 UI와 도메인 로직을 명확히 분리하지 못한 점이 아쉽습니다.
과제 목표 중에 하나였던 React Hook Form과 Zod 기반의 폼 구조 개선, 그리고 Dark Mode 구현은 이번 제출 범위에는 포함하지 못했지만, 이후에 더 완성도 있게 다듬어갈 계획입니다. 특히 Zod를 활용한 스키마 검증과 디자인 토큰을 활용한 다크모드는 이전부터 디자인 시스템을 구현하면서 해보고 싶었던 거라 끝까지 마무리 지어보려합니다!
리뷰받고 싶거나 질문하고 싶은 내용