앞선 포스팅에선 공통 컴포넌트를 만들기 위한 첫번째 방법인 도메인 로직과 뷰를 분리하는 방법에 대해서 알아보았다. 이번에는 두번째 방법인 확장성있는 UI
를 만들어보자.
이번 포스팅에서는 현재 진행중인 프로젝트에서 사용하는 버튼 컴포넌트를 예시로 들어보겠다.
"버튼"이 가지는 공통적인 특징은, (1) 버튼이름과 버튼 모양의 UI를 가진다. (2) 사용자와 인터렉션(클릭)하여 특정한 작업(이벤트 처리)을 수행한다.
그렇다면 변경될 수 있는 점은 외부에서 주입받고 버튼이 가지는 공통적인 특성만 추상화하여 구현해보자.
버튼 컴포넌트는 외부로부터 주입받는 색상, radius, 폰트 컬러, 크기에 따라 동적으로 스타일링되어야 한다.
사실 이글을 쓰게된 원인이 여기에 있다. 현재 프로젝트에서는 tailwind를 사용중이었다. 분명 클래스 이름은 잘 들어가는데 적용하고자하는 버튼 색이 보이지 않는 오류가 있었다.
tailwind 공식문서를 보고 해결방법을 알게되었다.
tailwind가 class들을 추출하는 방법은 다음과 같다.
모든 코드의 언어들이 아니라, 단지 정규 표현식을 사용하여 클래스 이름으로 가능성이 있는 모든 문자열을 추출합니다.
// ❌ 잘못된 예시 : 클래스 이름을 동적으로 구조화하면 안된다.
<div class="text-{{ error ? 'red' : 'green' }}-600"></div>
// ✅ 올바른 예시 : 항상 완전한 클래스 이름을 사용해야 한다.
<div class="{{ error ? 'text-red-600' : 'text-green-600' }}"></div>
// ❌ 잘못된 예시 : 프롭을 사용해서 동적으로 클래스 이름을 만들면 안된다.
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;