사진을 보시면 만들어야할 버튼들이 정말 많았습니다. 버튼 컴포넌트의 재사용성을 높이기 위해 저 많은 버튼을 한 컴포넌트에 넣어 Props로 사용할 버튼의 명칭을 전달해주면 사용할 수 있도록 하였습니다.
import style from './BaseButton.module.css';
import add from '../../assets/addButton.svg';
interface BaseButtonProps {
onClick?: () => void;
type?: "button" | "submit" | "reset";
text?: string;
leftImage?: string;
rightImage?: string;
styleType?:
| 'baseButton'
| 'login'
| 'loginBlock'
| 'accept'
| 'refuse'
| 'delete'
| 'dashboardDelete'
| 'newDashboard'
| 'addColumn'
| 'addColumnButton';
}
function BaseButton({
onClick,
type,
text,
leftImage,
styleType = 'baseButton',
rightImage,
}: BaseButtonProps) {
const buttonClassName = `${style[styleType]}`;
const addButton = rightImage === 'addButton' ? add : undefined;
return (
<button type={type} className={buttonClassName} onClick={onClick}>
{leftImage && (
<img className={style.baseButtonImg} src={leftImage} alt="button-img" />
)}{' '}
{text}{' '}
{rightImage && (
<img
className={style.baseButtonImg}
src={addButton}
alt="addButton-img"
/>
)}
</button>
);
}
export default BaseButton;
대충 봐도 문제점이 많이 보인다.
문제점
1. 너무 많은 Props를 전달해야함
2. 모든 버튼을 이 컴포넌트를 재사용하여 구현하려고 하니 코드가 복잡해짐
3. 요구사항이 늘어날 수록 컴포넌트가 복잡해질 가능성 매우 높음
4. 현재 코드는 특정 스타일과 이미지에 의존하여 재사용성이 떨어짐
위 단점을 해결하기 위해 다시 처음으로 돌아가 atomic 단위로 버튼 컴포넌트를 만들어야겠다고 생각이 들었습니다.
일단 먼저 크기만 다른 버튼들을 묶어 하나의 컴포넌트에서 관리할 수 있도록 하였습니다.
import React, { ButtonHTMLAttributes } from "react";
import style from "./BaseButton.module.css";
interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
className?: string;
}
function Button({ children, ...rest }: ButtonProps) {
return (
<button className={style.baseButton} {...rest}>
{children}
</button>
);
}
// 로그인 버튼
function LoginButton({ ...rest }: ButtonProps) {
return (
<Button
className={rest.disabled ? `${style.loginBlock}` : `${style.login}`}
{...rest}
/>
);
}
//수락 버튼
function AcceptButton({ ...rest }: ButtonProps) {
return <Button className={style.accept} {...rest} />;
}
//거절 버튼
function CancelButton({ ...rest }: ButtonProps) {
return <Button className={style.cancel} {...rest} />;
}
//삭제 버튼
function DeleteButton({ ...rest }: ButtonProps) {
return <Button className={style.delete} {...rest} />;
}
//모달에 들어갈 수락 버튼
function ModalColorButton({ ...rest }: ButtonProps) {
return <Button className={style.modalColor} {...rest} />;
}
//모달에 들어갈 취소 버튼
function ModalCancelButton({ ...rest }: ButtonProps) {
return <Button className={style.modalCancel} {...rest} />;
}
Button.Login = LoginButton;
Button.Accept = AcceptButton;
Button.Cancel = CancelButton;
Button.Delete = DeleteButton;
Button.ModalColor = ModalColorButton;
Button.ModalCancel = ModalCancelButton;
export default Button;
function InviteModal({ handleModalClose, dashboardId }: InviteModalProps) {
{...}
return (
<ModalContainer handleModalClose={handleModalClose}>
<CommonModalLayout title="초대하기">
<form className={styles.contents} onSubmit={handleSubmit(onSubmit)}>
<CommonInput
label="이메일"
placeholder="이메일을 입력해 주세요"
type="email"
validation={emailValidation}
errors={errors}
/>
<div className={styles.modal_buttons}>
<Button.ModalColor type="submit">초대</Button.ModalColor>
<Button.ModalCancel type="button" onClick={handleModalClose}>
취소
</Button.ModalCancel>
</div>
</form>
</CommonModalLayout>
</ModalContainer>
);
}
export default InviteModal;
먼저 버튼이 받는 Props에 타입을 새로 지정해주었습니다. ButtonHTMLAttributes<HTMLButtonElement>
을 타입을 지정해 버튼이 가질 수 있는 속성들을 Props로 전달해줄 수 있도록 하였습니다.
Button 컴포넌트를 만들어 기본 버튼을 구현합니다. 이후, 로그인 버튼, 수락 버튼, 거절 버튼, 삭제 버튼, 그리고 모달에 들어갈 수락 및 취소 버튼을 만들기 위해 Button 컴포넌트를 상속하여 새로운 버튼 컴포넌트를 구현합니다. 이러한 방식으로, 각 버튼 컴포넌트는 Button 컴포넌트를 기반으로 하면서도 필요에 따라 스타일과 동작을 추가할 수 있습니다.
이렇게 구현된 버튼 컴포넌트는 다음과 같은 장점을 가지고 있습니다
아래에 보이는 버튼들은 각자의 역할과 목적이 다르며, 또한 디자인도 서로 다릅니다. 이에 따라 이러한 다양한 기능과 디자인을 가진 버튼들을 컴파운드 패턴으로 하나의 컴포넌트로 묶어 구현하는 것보다는 각각의 버튼이 명확한 역할을 수행하고 그에 맞는 이름을 가지도록 따로 만드는 것이 적절합니다. 이렇게 하면 각 버튼이 자신의 역할과 목적을 명확하게 드러내며, 개별적으로 관리되어야 하는 기능과 디자인을 더 잘 표현할 수 있습니다.
import styles from "./CreateBoardButton.module.css";
import { createPortal } from "react-dom";
import DashBoardCreateModal from "../../Modal/DashBoardCreateModal";
import { useModal } from "../../../hooks/useModal";
function CreateBoardButton() {
const { isModalOpen, handleModalOpen, handleModalClose } = useModal();
return (
<>
{createPortal(
isModalOpen && (
<DashBoardCreateModal handleModalClose={handleModalClose} />
),
document.body
)}
<div className={styles.container} onClick={handleModalOpen}>
<p className={styles.font}>새로운 대시보드</p>
<img src="/Icons/large.svg" alt="add_dashboard" />
</div>
</>
);
}
export default CreateBoardButton;
대시보드 생성 버튼을 클릭하면 대시보드 생성을 도와주는 모달을 띄워야 합니다. 다른 버튼들도 모달을 보여주는 버튼이 있을 수 있지만, 그렇지 않은 버튼도 있을 겁니다. 각 버튼이 다른 기능을 수행한다면, 버튼을 분리하여 각 버튼이 담당하는 기능과 목적이 명확히 드러나도록 하는 것이 좋습니다.
따라서, 대시보드 생성 버튼과 같이 모달을 보여주는 버튼은 그 역할을 명확히 드러내는 별도의 컴포넌트로 분리하는 것이 적절하다고 생각하여 분리하였습니다. 이렇게 하면 해당 버튼의 기능을 더 명확하게 이해하고 관리할 수 있습니다.
다른 버튼 컴포넌트들도 각각의 기능과 목적에 따라 별도로 만들어졌습니다.
어떻게 했는지 궁금하면 밑에 깃허브 링크를 클릭해주세요
📌 Taskify 깃허브 링크
https://github.com/taskify-team7/taskify