예를 들어 사용자의 프로필정보를 보여주는 UI 컴포넌트를 개발하고 있다고 하자. UI에는 사용자의 이름, 프로필 사진, 그리고 몇 가지 추가 정보(예: 이메일, 전화번호 등)가 포함한다고 가정한다.
초기에는 각 정보를 개별 props로 전달하여 중첩된 컴포넌트로 구성되어 있다.
// ProfileComponents.tsx
import React from 'react';
interface ProfileProps {
name: string;
email: string;
phoneNumber: string;
avatarUrl: string;
}
const Profile: React.FC<ProfileProps> = ({ name, email, phoneNumber, avatarUrl }) => (
<div>
<ProfileAvatar avatarUrl={avatarUrl} />
<ProfileName name={name} />
<ProfileInfo email={email} phoneNumber={phoneNumber} />
</div>
);
interface ProfileAvatarProps {
avatarUrl: string;
}
const ProfileAvatar: React.FC<ProfileAvatarProps> = ({ avatarUrl }) => (
<img src={avatarUrl} alt="Profile Avatar" />
);
interface ProfileNameProps {
name: string;
}
const ProfileName: React.FC<ProfileNameProps> = ({ name }) => (
<h1>{name}</h1>
);
interface ProfileInfoProps {
email: string;
phoneNumber: string;
}
const ProfileInfo: React.FC<ProfileInfoProps> = ({ email, phoneNumber }) => (
<div>
<p>Email: {email}</p>
<p>Phone: {phoneNumber}</p>
</div>
);
export default Profile;
컴파운드 컴포넌트 패턴을 적용하여 Profile 컴포넌트를 더 유연하고 재사용 가능하게 리팩토링할 수 있다.
이 패턴을 사용하면 각 하위 컴포넌트를 필요에 따라 조합하여 사용할 수 있으며 각 컴포넌트 props를 직접 관리할 수 있다.
import React from "react";
// Profile 관련 Props 정의
interface ProfileProps {
children: React.ReactNode;
}
interface ProfileAvatarProps {
avatarUrl: string;
}
interface ProfileNameProps {
name: string;
}
interface ProfileInfoProps {
email: string;
phoneNumber: string;
}
// Profile 컴포넌트와 그 하위 컴포넌트 타입 정의
interface ProfileComposition {
Avatar: React.FC<ProfileAvatarProps>;
Name: React.FC<ProfileNameProps>;
Info: React.FC<ProfileInfoProps>;
}
// Profile 컴포넌트
export const Profile: React.FC<ProfileProps> & ProfileComposition = ({ children }) => {
return <div>{children}</div>;
};
// Profile.Avatar 컴포넌트
const ProfileAvatar: React.FC<ProfileAvatarProps> = ({ avatarUrl }) => {
return <img src={avatarUrl} alt="Profile" />;
};
// Profile.Name 컴포넌트
const ProfileName: React.FC<ProfileNameProps> = ({ name }) => {
return <h2>{name}</h2>;
};
// Profile.Info 컴포넌트
const ProfileInfo: React.FC<ProfileInfoProps> = ({ email, phoneNumber }) => {
return (
<div>
<p>Email: {email}</p>
<p>Phone: {phoneNumber}</p>
</div>
);
};
// Profile 컴포넌트에 하위 컴포넌트 할당
Profile.Avatar = ProfileAvatar;
Profile.Name = ProfileName;
Profile.Info = ProfileInfo;
리팩토링된 Profile 컴포넌트를 사용하는 방법은 다음과 같다.
import React from "react";
import { Profile } from "./Profile";
function UserProfile() {
return (
<Profile>
<Profile.Avatar avatarUrl="https://example.com/avatar.jpg" />
<Profile.Name name="Jane Doe" />
<Profile.Info email="jane.doe@example.com" phoneNumber="123-456-7890" />
</Profile>
);
}
export default UserProfile;
Profile 컴포넌트는 ProfileProps를 통해 필요한 모든 프로필 데이터를 받고 children을 통해 받은 하위 컴포넌트들에게 이 데이터를 전달한다.
React.Children.map과 React.cloneElement를 사용하여 Profile의 children으로 받은 각 컴포넌트에 필요한 props를 전달한다.
이 방식을 사용하면 Profile 컴포넌트를 사용하는 개발자가 하위 컴포넌트에 어떤 props가 필요한지 명시적으로 알 필요 없이 Profile 컴포넌트를 통해 자동으로 관련 데이터를 전달받을 수 있다.
각 하위 컴포넌트(ProfileAvatar, ProfileName, ProfileInfo)는 필요한 데이터를 props로 직접 받아 사용한다.
이로써 각 컴포넌트는 독립적으로도 재사용이 가능하며 Profile 컴포넌트의 구조 내에서도 유연하게 활용될 수 있다.