[리팩토링] 재사용 가능한 버튼 컴포넌트 만들기

지은·2023년 5월 19일
1

🌴 모동숲 마켓

목록 보기
9/9
post-thumbnail

모동숲 마켓에서 사용하는 여러번 사용하는 버튼 디자인은 크게 3가지가 있다.
1. 민트색 버튼 2. 흰색 버튼 3. 회색 버튼

TailwindCSS를 사용해서 만들면서 그때 그때 className을 복사해서 만들었는데, 디자인이 겹치기 때문에 버튼 컴포넌트를 만들어서 재사용하여 코드의 중복을 줄이고 디자인을 일관되게 유지하도록 하려고 한다.
컴포넌트를 사용하는 곳에서 props를 통해 색상, 사이즈, 클릭 이벤트 핸들러 등을 전달할 수 있다.

Button.tsx

import { ReactNode } from 'react';

type Color = 'mint' | 'white' | 'gray';
type Size = 'sm' | 'md' | 'lg';

interface ButtonProps {
	type?: 'submit' | undefined; // type이 undefined인 경우에는 'button'으로 지정
	color: Color;
	size: Size;
	className?: string; // 추가로 적용하고 싶은 className이 있을 경우 사용
	onClick?: () => void;
	children: ReactNode; // children 속성에는 string 뿐만 아니라 <svg> 요소가 포함될 수도 있어서 ReactNode 타입을 사용했다.
}

function Button({ type, color, size, className, onClick, children }: ButtonProps) {
	let combinedClassName = ''; // 이 변수에 className을 중첩시킨다.

	switch (color) {
		case 'mint': {
			combinedClassName = 'mr-2 rounded-lg border border-mint bg-mint font-semibold text-white hover:bg-hover-mint focus:ring-ring-mint';
			break;
		}
		case 'white': {
			combinedClassName = 'mr-2 rounded-lg border border-mint bg-transparent font-semibold text-mint  hover:bg-gray-100 focus:ring-gray-300';
			break;
		}
		case 'gray': {
			combinedClassName =
				'inline-flex items-center rounded-lg border border-gray-300 bg-white text-center font-medium text-gray-900 hover:bg-gray-100 focus:outline-none focus:ring-gray-200';
			break;
		}
	}

	switch (size) {
		case 'sm': {
			combinedClassName += ' py-1.5 px-3 text-sm focus:ring-4';
			break;
		}
		case 'md': {
			combinedClassName += ' py-2 px-4 text-sm focus:ring-2';
			break;
		}
		case 'lg': {
			combinedClassName += ' py-2 px-4 text-base focus:ring-4';
			break;
		}
	}

	return (
		<button type={type ? type : 'button'} className={`${combinedClassName} ${className}`} onClick={onClick}>
			{children}
		</button>
	);
}

export default Button;

EditProfile.tsx

<Button onClick={onSubmit} color='mint' size='md'>
  변경
</Button>
<Button onClick={toggleIsEditing} color='white' size='md'>
  취소
</Button>

시도했던 다른 방법 (@layer 디렉티브)

사실 이 방법 이전에는 index.css 파일에 @layer components 디렉티브 내부에 버튼마다 클래스를 작성했는데, 이 방법은 코드의 유지 보수성과 가독성에도 좋지 않고, CSS 파일이 더 복잡해지고 읽기 어려워지기 때문에 일반적으로 권장되지 않는다고 한다.

@layer components { /* ❌ */
  button {
    &.mint {
      ...
    }
    &.white {
      ...
    }
  }
}
  • 복잡성 증가
    컴포넌트 스타일을 관리하기 위해 레이어를 만들 수는 있지만, 너무 많은 레이어를 사용하면 관련 클래스를 찾기 어려워질 수 있다.
  • 레이어 간 의존성
    많은 @layer 디렉티브를 사용하면 레이어 간의 의존성이 증가할 수 있다. 예를 들어, 하나의 레이어에서 정의된 클래스가 다른 레이어에서 사용되는 경우, 코드를 이해하기 어려워질 수 있고 스타일 충돌이 발생할 수도 있다.
  • 관심사 분리 어려움
    index.css 파일은 주로 전역 스타일을 정의하는 데 사용된다. 많은 @layer 디렉티브를 사용하면 전역 스타일과 컴포넌트 스타일이 섞이게 되어 관심사 분리가 어려워질 수 있다.

➡️ 따라서 컴포넌트를 단독으로 유지하고 재사용 가능하게 하려면, 컴포넌트 별로 스타일을 분리하는 것이 좋다.

그리고 index.css 파일에서 @layer 디렉티브를 사용하는 경우는 전역 스타일을 정의하거나 필요한 경우에만 컴포넌트 관련 스타일을 추가하는 방식으로 사용하는 것이 코드의 가독성과 유지보수성에 좋다.

profile
개발 공부 기록 블로그

6개의 댓글

comment-user-thumbnail
2023년 5월 19일

지은님 코드 본적이 있는데 switch를 되게 잘쓰시는거 같아요 컴포넌트화 읽기도 쉽고 간편해서 잘작성하신거같네요! 굿입니다

답글 달기
comment-user-thumbnail
2023년 5월 21일

확실히 하나 만들어두고 쓰는게 편한 것 같아용 ㅎㅎ

답글 달기
comment-user-thumbnail
2023년 5월 21일

깔끔하네요 ! 잘 보고 갑니다

답글 달기
comment-user-thumbnail
2023년 5월 21일

잘보고 갑니당! 테일윈드는 편한데 참 className 고봉밥 되기 쉬워서 더러워지는 거 같아요. 근데 if문 대신 switch를 써서 가독성이 좋아졌긴 합니다 ㅎㅎ. 다음엔 CSS 파일 만들어서 거기에 btn 관련 스타일을 선언 후 넣어서 코드분리를 해보는 건 어떨까요? 잘보고 갑니다

답글 달기
comment-user-thumbnail
2023년 5월 21일

전글에도 얘기했는데 style/ui.css 등의 파일을 만들고 .mint-btn : bg-mint ... 이런 식으로 해보세요! 전역 선언 layout은 잘 안쓰긴 하드라구요 ㅎㅎ

1개의 답글