Studying/React

리액트 타입스크립트 공부하기 - props로 테일윈드 className 속성 내려주기

creamymood 2025. 6. 23. 15:21

팀원분들이랑 공통 컴포넌트 만드는 걸 역할 분담 했었는데

 

만들어진 인풋 컴포넌트 창을 바로 가져다 쓰려고 하니까

앞선 팀원분께서 인풋창에 사이즈를 고정으로 해두니까..

css에 대한 확장성이 없어서,

(완전히 고정된 고정값만, 공통 인풋 컴포넌트에서 지정해주고)

 

className으로 아예 테일 윈드 스타일을 프롭스로 내려줘서 

공통 인풋창이나 버튼창을 import 해서 쓰는 곳에서 아예 하드 코딩 하는 방법을 선택하기로 했다 !! 

 

프로젝트 하며 정말 진짜 할게 많지만, 배우는게 많고

정신 똑바로 차리려다보니까 좋은 점과 배울점이 더 많다 - ♥︎


테일윈드 쓰고 있다면 공통 버튼 컴포넌트에서 className을 prop으로 내려주는 방식이 제일 일반적이고 유연하다.

이렇게 하면 외부에서 스타일을 자유롭게 확장하거나 덮어쓸 수 있어서 좋다.

 

// Button.tsx (공통 버튼 컴포넌트)
import React from 'react';

interface ButtonProps {
  children: React.ReactNode;
  onClick?: () => void;
  className?: string; // 외부에서 스타일 덮어쓸 수 있게
}

export default function Button({ children, onClick, className = '' }: ButtonProps) {
  return (
    <button
      onClick={onClick}
      className={`px-4 py-2 rounded-md bg-blue-500 text-white hover:bg-blue-600 ${className}`}
    >
      {children}
    </button>
  );
}

 

className = '' 이 부분 설명 ↓

더보기

className = '' 이 부분은 기본값(default value) 설정이야.

export default function Button({ children, onClick, className = '' }: ButtonProps)

여기서 className = ''은 className이라는 prop이 전달되지 않았을 때, 기본값을 빈 문자열로 설정하겠다는 뜻이야.

 

왜 이렇게 쓰는 걸까?

아래처럼 쓰는 JSX 코드가 있다고 가정해보자:

<Button onClick={handleClick}>Click</Button>

이 경우 className은 아예 전달되지 않지?
그러면 undefined야.

그런데 만약 버튼 내부에서 className을 이렇게 처리하고 있다면:

className={`base-class ${className}`}

여기서 className이 undefined면 어떻게 될까?

undefined는 문자열로 변환되면서 "base-class undefined"가 되어버려.
→ 이러면 HTML에 class="base-class undefined" 이런 식으로 들어가서 예상치 못한 스타일 오류가 날 수 있어.


그래서 = '' 을 주는 이유

className = ''으로 기본값을 빈 문자열로 설정하면:

<Button>Click</Button>  // className 안 줘도 됨
className={`base-class ${className}`}  // 항상 문자열 + 문자열이 되니까 안전함

즉, 안전하게 템플릿 문자열을 조립하기 위해 기본값으로 빈 문자열을 지정하는 거야.

 

정리


className = '' prop이 없을 경우에도 undefined 대신 빈 문자열로 설정
className={... ${className}} 문자열 병합 시 오류나 "undefined" 텍스트 삽입 방지
className={`px-4 py-2 rounded-md bg-blue-500 text-white hover:bg-blue-600 ${className}`}

 

해당부분 설명 ↓

더보기
className={`px-4 py-2 rounded-md bg-blue-500 text-white hover:bg-blue-600 ${className}`}

여기서 ${className}은 외부에서 전달된 스타일을 덧붙이기 위해서 쓰는 거야.

= 내려준 자식 컴포넌트에서 하드코딩 할 부분! 

앞에 px-4~ hover까지가 부모인 공통 컴포넌트에서 고정해준 고정 값이다

그리고 사용 쪽에서는 이렇게:

<Button className="bg-red-500 hover:bg-red-600 text-black">
  커스텀 버튼
</Button>

 


디폴트, 호버, 액티브, 디스에이블 이 값은 부모에서 디자인 해두고 밑으로 상태를 내려서 상태에 따라 조건부 렌더링 인건가 ?

 

 

→ 응, 정확해!
보통은 디자인 시스템이 있는 경우, 버튼의 상태(default, hover, active, disabled)에 따라 스타일을 어떻게 줄지 상위 컴포넌트에서 결정하고, 그걸 props로 내려서 버튼 내부에서 조건부로 className을 적용하는 방식이 일반적이야.

 

구조 예시

1. Button.tsx (공통 컴포넌트)

interface ButtonProps {
  children: React.ReactNode;
  status?: 'default' | 'hover' | 'active' | 'disabled';
  onClick?: () => void;
  className?: string;
}

export default function Button({ children, status = 'default', onClick, className = '' }: ButtonProps) {
  const base = 'px-4 py-2 rounded text-white';
  
  const statusClass = {
    default: 'bg-blue-500 hover:bg-blue-600',
    hover: 'bg-blue-600',
    active: 'bg-blue-700',
    disabled: 'bg-gray-400 cursor-not-allowed',
  };

  const isDisabled = status === 'disabled';

  return (
    <button
      onClick={onClick}
      disabled={isDisabled}
      className={`${base} ${statusClass[status]} ${className}`}
    >
      {children}
    </button>
  );
}

2. 사용하는 쪽 (부모 컴포넌트)

<Button status="active">활성 버튼</Button>
<Button status="disabled" onClick={() => alert('눌림')}>비활성</Button>

💡 참고 포인트

  • hover 상태는 보통 :hover로 처리하지만,
    강제로 hover 상태 디자인을 보여주고 싶을 때는 이렇게 status로 내려서 강제 적용할 수도 있어.
  • 보통은 hover와 active는 유저가 마우스로 조작할 때 자동으로 되는 스타일이라, status로 관리하는 건 디자인 시스템에서 미리 보여주거나, 상태 미리보기 용도로 사용하는 경우가 많아.