아이콘 Toggle 버튼이 아이콘만 다르고 구동방식이 동일하기때문에 리팩토링하여 공통 인터페이스와 로직을 공유하고 아이콘을 사용자 정의. 아이콘에 필요한 props(예: "켜짐" 및 "꺼짐" 상태의 아이콘 이름)를 허용하는 일반적인 ToggleButton 구성 요소를 만들면 코드 중복을 피하고 로직을 일관되게 유지할 수 있다.
Step 1: 공유 인터페이스 및 구성 요소 만들기
먼저 모든 공유 속성을 포함하는 공통 인터페이스 ToggleButtonProps를 정의하고 두 아이콘 이름(activeIcon 및 inactiveIcon)에 대한 props를 추가합니다.
import { Icon } from "@/components/common/Icon";
import { useEffect, useState } from "react";
interface ToggleButtonProps {
isActive: boolean; // 토글 상태 (부모 컴포넌트에서 제어)
onToggle?: (newState: boolean) => void; // 토글 상태가 바뀔 때 부모에게 알림
className?: string; // 선택적으로 Tailwind CSS 같은 클래스명을 전달
readonly?: boolean;
activeIcon: string; // 활성화 상태의 아이콘 이름
inactiveIcon: string; // 비활성화 상태의 아이콘 이름
activeIconClass?: string; // 활성화 상태의 아이콘 클래스
inactiveIconClass?: string; // 비활성화 상태의 아이콘 클래스
}
const ToggleButton = ({
isActive = false,
onToggle,
className,
readonly = false,
activeIcon,
inactiveIcon,
activeIconClass = "size-[18px] text-primary", // 기본 클래스
inactiveIconClass = "size-[18px] text-gray-300", // 기본 클래스
}: ToggleButtonProps) => {
const [active, setActive] = useState(isActive);
// 부모로부터 받은 초기 상태가 변경되면 업데이트
useEffect(() => {
setActive(isActive);
}, [isActive]);
// 토글 버튼 클릭 시 호출
const handleClick = () => {
if (readonly) return;
const newState = !active; // 현재 상태 반전
setActive(newState); // 내부 상태 업데이트
// 부모 컴포넌트에 onToggle 콜백이 있으면 호출
if (onToggle) {
onToggle(newState);
}
};
return (
<button type="button" onClick={handleClick} className={className}>
{active ? (
<Icon iconName={activeIcon} className={activeIconClass} />
) : (
<Icon iconName={inactiveIcon} className={inactiveIconClass} />
)}
</button>
);
};
export default ToggleButton;
Step 2: ImportantToggle 및 StarToggle에 ToggleButton 구성 요소 사용
ToggleButton 구성 요소를 재사용하고 각각에 대한 특정 아이콘을 전달하여 ImportantToggle 및 StarToggle 구성 요소
ImportantToggle Component
//ImportantToggle Component
import ToggleButton from "@/components/common/ToggleButton";
interface ImportantToggleProps {
isImportant: boolean;
onToggle?: (newState: boolean) => void;
className?: string;
readonly?: boolean;
}
const ImportantToggle = ({
isImportant = false,
onToggle,
className,
readonly = false,
}: ImportantToggleProps) => {
return (
<ToggleButton
isActive={isImportant}
onToggle={onToggle}
className={className}
readonly={readonly}
activeIcon="AlertFill"
inactiveIcon="Alert"
activeIconClass="size-[18px] text-primary"
inactiveIconClass="size-[18px] text-gray-300"
/>
);
};
export default ImportantToggle;
StarToggle Component:
import ToggleButton from "@/components/common/ToggleButton";
interface StarToggleProps {
isStarred: boolean;
onToggle?: (newState: boolean) => void;
className?: string;
readonly?: boolean;
}
const StarToggle = ({
isStarred = false,
onToggle,
className,
readonly = false,
}: StarToggleProps) => {
return (
<ToggleButton
isActive={isStarred}
onToggle={onToggle}
className={className}
readonly={readonly}
activeIcon="StarFill"
inactiveIcon="Star"
activeIconClass="size-[18px] text-[#FFD217]"
inactiveIconClass="size-[18px] text-gray-300"
/>
);
};
export default StarToggle;
Summary of Changes:
이 코드를 리팩터링한 이유는 중복된 로직을 하나의 공통 컴포넌트로 추출해서 코드 재사용성을 높이기 위해서다. ImportantToggle과 StarToggle은 기능이 동일하고 아이콘만 다른 상황이므로, 두 컴포넌트가 공유하는 로직을 분리하고 아이콘만 다르게 전달할 수 있도록 만듬. 이렇게 하면 유지보수가 용이해지고, 새로운 토글 버튼이 필요할 때도 쉽게 확장할 수 있다.
- isActive: 토글 상태 (기본값 false)
- onToggle: 부모 컴포넌트에 상태 변화를 알리기 위한 콜백 함수
- className: 추가 스타일을 위한 클래스
- readonly: 읽기 전용 상태 여부
- activeIcon과 inactiveIcon: 각각 토글이 활성화되었을 때와 비활성화되었을 때의 아이콘 이름
- activeIconClass와 inactiveIconClass: 각각의 아이콘에 적용할 CSS 클래스
1. React에서 상속보다 구성을 선호하는 이유
- React의 철학: React는 컴포넌트를 기반으로 UI를 구성하는 라이브러리입니다. 구성(Composition)은 다양한 컴포넌트를 조합하여 더 큰 시스템을 만드는 방식이기 때문에, UI 개발에서는 상속보다 훨씬 직관적이고 유연합니다.
- **단일 책임 원칙(Single Responsibility Principle)**에 맞게 컴포넌트를 작게 쪼개고, 이를 조합하여 더 큰 기능을 구현하는 것이 더 직관적이고 관리하기 쉽기 때문다.
- UI의 재사용성: 상속은 특정한 경우에 유용할 수 있지만, UI는 재사용성과 커스터마이징이 중요하기 때문에 상속보다는 구성 방식을 사용하면 더 쉽게 관리할 수 있다.
- 다중 상속의 부재: 자바스크립트는 단일 상속만 지원. 즉, 한 클래스는 하나의 부모 클래스만 상속받을 수 있다. 반면, 구성 방식을 사용하면 다양한 컴포넌트와 기능을 조합하여 사용 가능하므로, 상속의 제약을 피할 수 있다.
2. 구성을 통한 확장의 유연성
구성 방식에서는 상속의 계층 구조를 따르지 않기 때문에 기능 확장이 훨씬 자유롭다. 예를 들어, ToggleButton에 추가적인 기능을 넣고 싶다면 컴포넌트를 그대로 사용하되 다른 컴포넌트와 함께 조합하거나, props나 children을 통해 동작을 조정할 수 있다.
구성 방식에서 확장을 위한 방법:
- Props를 이용하여 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하고 동작을 제어.
- Higher-Order Components (HOCs) 또는 Render Props 패턴을 이용하여 동작을 변경하거나 추가적인 로직을 쉽게 주입.
- Hooks와 같은 React 기능을 활용하여 로직을 캡슐화하고, 다양한 컴포넌트에서 쉽게 재사용.
정리하며..
• ImportantToggle과 StarToggle은 기능은 동일하고 아이콘만 다르기 때문에 공통 로직을 ToggleButton으로 추출해 재사용성을 높임
• 중복된 토글 상태 관리, 아이콘 렌더링, 클릭 핸들링 로직을 하나의 컴포넌트로 일원화하여 코드 유지보수성과 확장성 향상
• 각 토글 버튼은 고유한 아이콘(activeIcon, inactiveIcon)과 스타일을 props로 전달만 하면 되기 때문에 새로운 토글 버튼 생성도 간편
• 이 방식은 React의 “상속보다 구성” 철학에 부합하며, 필요한 기능만 props나 조합을 통해 유연하게 확장 가능
🧩 요약된 장점
• ✅ 로직 재사용성 향상 – 중복 코드 제거
• 🔄 상태 동기화 유지 – 내부 상태와 외부 상태 일치
• 🎯 명확한 props 인터페이스 – 커스터마이징 용이
• 🔧 확장성 높은 구성 방식 – 향후 새로운 기능 추가에 유리
'Dev > Nextjs' 카테고리의 다른 글
| [Nextjs] Nextjs + nprogress 구현 (0) | 2024.09.30 |
|---|---|
| [React] React.FC vs React.ButtonHTMLAttributes - 공통컴포넌트 제작방식 (0) | 2024.09.25 |
| [SVGR] SVG 파일 컴포넌트 사용 (0) | 2024.09.22 |
| [Nextjs]Vercel + TypeORM (0) | 2024.09.05 |
| Nestjs 설정하기 (0) | 2024.09.05 |