TypeScript 기반 스타일 관리

데브코스

목록 보기
95/131

내가 지금 처음 타입스크립트 라는 것을 공부하다보니, 자바스크립트나 리액트와는 다르게, 타입을 정말 하나하나 전부 다 지정을 해두더라고,
그리고 타입을 또 만들어서 이 타입은 이런 이런 타입만 있고, 이런 걸 만들어주던데, 그거에 대해서 어떻게 작성하는지 정말 많이 만들었던 버튼 컴포넌트를 가지고 설명해볼게.

Button.tsx

Button 컴포넌트와 Theme의 관계

전체 구조 한눈에 보기

theme.ts (설계도)          Button.tsx (실제 사용)
     ↓                           ↓
"버튼은 3가지 크기"  →  size="large" 선택
"각 크기마다 폰트, 패딩"  →  자동으로 스타일 적용

1. theme.ts의 역할

"모든 디자인 규칙을 한 곳에 모아둔 설계도"

타입 정의 부분

export type ButtonSize = "large" | "medium" | "small";
export type ButtonScheme = "primary" | "normal";

의미:

  • "버튼 크기는 이 3가지만 가능해!"
  • "버튼 스타일은 이 2가지만 가능해!"
  • 오타 방지 + 자동완성

실제 값 정의 부분

button: {
  large: {
    fontSize: "1.5rem",
    padding: "1rem 2rem",
  },
  medium: {
    fontSize: "1rem",
    padding: "0.5px 1px",
  },
  // ...
}

의미:

  • large 버튼은 이렇게 생겨야 함
  • medium 버튼은 이렇게 생겨야 함
  • 모든 크기 정보를 여기에 집중

2. Button.tsx의 역할

"theme의 규칙을 따라서 실제 버튼 만드는 곳"

Props 인터페이스

interface Props {
  children: React.ReactNode;  // 버튼 안 텍스트
  size: ButtonSize;           // theme에서 정의한 타입!
  scheme: ButtonScheme;       // theme에서 정의한 타입!
  disabled?: boolean;         // 비활성화 여부
  isLoading?: boolean;        // 로딩 중 여부
}

의미:

  • size에는 "large" | "medium" | "small"만 들어갈 수 있음
  • 다른 거 넣으면 TypeScript가 에러냄

스타일 적용 부분

const ButtonStyle = styled.button<Omit<Props, "children">>`
  font-size: ${({ theme, size }) => theme.button[size].fontSize};
  padding: ${({ theme, size }) => theme.button[size].padding};

해석:

  • size="large" 전달하면
  • theme.button["large"].fontSize 가져옴
  • theme.button["large"].padding 가져옴
  • 자동으로 1.5rem, 1rem 2rem 적용됨!

3. 이 구조의 장점

장점 1: 디자인 통일

// 나쁜 예 (각자 맘대로)
<button style={{ fontSize: "16px" }}>버튼1</button>
<button style={{ fontSize: "1rem" }}>버튼2</button>
<button style={{ fontSize: "18px" }}>버튼3</button>

// 좋은 예 (규칙 따름)
<Button size="medium">버튼1</Button>
<Button size="medium">버튼2</Button>
<Button size="medium">버튼3</Button>
// 전부 똑같은 크기로 나옴!

장점 2: 유지보수 쉬움

// theme.ts에서 한 줄만 바꾸면
button: {
  large: {
    fontSize: "2rem",  // 1.5rem → 2rem으로 변경
  }
}

// 모든 large 버튼이 자동으로 바뀜!
// 100개 파일 수정 안 해도 됨

장점 3: 오타 방지

// ❌ 에러남 (오타)
<Button size="big">버튼</Button>

// ✅ 자동완성됨
<Button size="large">버튼</Button>
//          ^ 여기서 large/medium/small 자동완성

4. 데이터 흐름

1. theme.ts에서 규칙 정의
   ↓
2. ThemeProvider로 전체 앱에 전달
   ↓
3. Button.tsx에서 theme 받아옴
   ↓
4. size prop에 따라 적절한 스타일 선택
   ↓
5. 화면에 버튼 렌더링

코드로 보면

// App.tsx
<ThemeProvider theme={light}>  // ← light 테마 전달
  <Button size="large">클릭</Button>  // ← large 요청
</ThemeProvider>

// Button.tsx 내부에서
theme.button["large"].fontSize  // ← "1.5rem" 가져옴

5. Omit이 뭐야?

const ButtonStyle = styled.button<Omit<Props, "children">>`

의미:

  • Props에서 children 빼고 나머지만 사용
  • children은 HTML 속성이 아니라서 제외함
// Props 전체
{ children, size, scheme, disabled, isLoading }

// Omit<Props, "children">
{ size, scheme, disabled, isLoading }  // children 제외됨

6. 실제 사용 예시

// 사용하는 쪽
function HomePage() {
  return (
    <>
      <Button size="large" scheme="primary">
        구매하기
      </Button>
      
      <Button size="medium" scheme="normal" disabled>
        품절
      </Button>
    </>
  );
}

결과:

  • 첫 번째 버튼: 큰 사이즈 + 파란 배경
  • 두 번째 버튼: 중간 사이즈 + 회색 배경 + 비활성화

정리

theme.ts

  • 디자인 시스템 정의
  • 모든 스타일 규칙을 한 곳에 모음
  • 타입도 같이 정의

Button.tsx

  • theme의 규칙을 사용
  • props로 원하는 스타일 선택
  • TypeScript가 오타 방지

왜 이렇게 하냐?

  • 일관성: 모든 버튼이 똑같은 규칙 따름
  • 유지보수: theme만 수정하면 전체 반영
  • 안전성: TypeScript가 실수 방지

"디자인 시스템을 코드로 만든 것"임!

profile
Dive Head First | Work Super Hard | Attract Great People

0개의 댓글