Tailwind에서 동적 스타일링 하기

이정수·2025년 1월 17일
0

앞선 포스팅에선 공통 컴포넌트를 만들기 위한 첫번째 방법인 도메인 로직과 뷰를 분리하는 방법에 대해서 알아보았다. 이번에는 두번째 방법인 확장성있는 UI를 만들어보자.

이번 포스팅에서는 현재 진행중인 프로젝트에서 사용하는 버튼 컴포넌트를 예시로 들어보겠다.

"버튼"이 가지는 공통적인 특징은, (1) 버튼이름과 버튼 모양의 UI를 가진다. (2) 사용자와 인터렉션(클릭)하여 특정한 작업(이벤트 처리)을 수행한다.

그렇다면 변경될 수 있는 점은 외부에서 주입받고 버튼이 가지는 공통적인 특성만 추상화하여 구현해보자.

(1) 버튼이름과 버튼 모양의 UI를 가진다

  • 버튼 이름 은 언제든지 변경가능한 사항이므로 props로 받는다.
  • 버튼 모양 으로 뭉뚱그리긴 했지만 좀 더 세분화 하면 색상, radius, 폰트 컬러, 크기 등이 있다. 이중 자주 변경되어야 하는 요소인 색상, radius, 폰트 컬러를 props로 받는다.

(2) 사용자와 인터렉션하여 특정한 작업을 수행한다.

  • 만들 버튼 컴포넌트는 UI를 "보여주는"데만 집중하자. onClick 함수를 props로 받는다.

동적 스타일링

버튼 컴포넌트는 외부로부터 주입받는 색상, radius, 폰트 컬러, 크기에 따라 동적으로 스타일링되어야 한다.

사실 이글을 쓰게된 원인이 여기에 있다. 현재 프로젝트에서는 tailwind를 사용중이었다. 분명 클래스 이름은 잘 들어가는데 적용하고자하는 버튼 색이 보이지 않는 오류가 있었다.

tailwind 공식문서를 보고 해결방법을 알게되었다.

tailwind가 class들을 추출하는 방법은 다음과 같다.

모든 코드의 언어들이 아니라, 단지 정규 표현식을 사용하여 클래스 이름으로 가능성이 있는 모든 문자열을 추출합니다.

Always use complete class names

// ❌ 잘못된 예시 : 클래스 이름을 동적으로 구조화하면 안된다.
<div class="text-{{ error ? 'red' : 'green' }}-600"></div>

// ✅ 올바른 예시 : 항상 완전한 클래스 이름을 사용해야 한다.
<div class="{{ error ? 'text-red-600' : 'text-green-600' }}"></div>

Always map props to static class names

// ❌ 잘못된 예시 : 프롭을 사용해서 동적으로 클래스 이름을 만들면 안된다.
function Button({ color, children }) {
  return (
    <button className={`bg-${color}-600 hover:bg-${color}-500 ...`}>
      {children}
    </button>
  )
}

// ✅ 올바른 예시 : 프롭은 이미 만들어진 클래스이름묶음을 매핑하는데 사용해야 한다.
function Button({ color, children }) {
  const colorVariants = {
    blue: 'bg-blue-600 hover:bg-blue-500',
    red: 'bg-red-600 hover:bg-red-500',
  }

  return (
    <button className={`${colorVariants[color]} ...`}>
      {children}
    </button>
  )
}

문서를 보고 나니 내가 두번째 잘못된 예시를 저지르고 있다는 점을 깨달았다..! (공식문서의 중요성..)

이제 올바른 방식으로 고쳐보자.

import React from 'react';

const bgColorVariants = {
  primary: 'bg-primary hover:bg-primaryDark',
  blue: 'bg-blue-400 hover:bg-blue-500',
} as const;

const roundedVariants = {
  full: 'rounded-full',
  sm: 'rounded-sm',
  md: 'rounded-md',
} as const;

interface IButton {
  type: 'button' | 'submit' | 'reset';
  label: string;
  rounded?: keyof typeof roundedVariants;
  color?: keyof typeof bgColorVariants;
  onClick?: () => void;
}

const Button = ({type, label, rounded = 'full', color = 'primary', onClick}: IButton) => {
  return (
    <button
      type={type}
      onClick={onClick}
      className={`px-5 py-2 text-xs text-white hover:cursor-pointer drop-shadow-xl my-2 
        ${bgColorVariants[color]}
        ${roundedVariants[rounded]}
        `}
    >
      {label}
    </button>
  );
};
export default Button;
  • bgColorVariants, roundedVariants의 키타입을 정의해서 Button 컴포넌트 사용처에서 올바른 클래스 키값만을 입력할 수 있도록 강제해줬다.
  • 이전 포스팅에서 올렸던 초록색 동그란 버튼 대신, prop만으로 파란색 네모난 버튼이 잘 생성되는 것을 볼수 있다..!

profile
keep on pushing

0개의 댓글