frontend/디자인 패턴
[REACT] 컴파운드(조합) 컴포넌트
프론트엔드코린이
2025. 1. 21. 14:27
컴파운드 컴포넌트란?
- 하나의 큰 컴포넌트(자이언트 컴포넌트)를 세부 요소로 쪼개놓고, Root에 assign 하는 형태로 합성하는 것입니다.
- prop을 사용하지 않고 내부에서 데이터를 처리할 수 있고, 비슷한 디자인의 컴포넌트가 필요할 때 새로운 컴포넌트를 만들지 않고 사용할 수 있습니다.
- 컴파운드 컴포넌트?? 용어부터 어렵잖아....?
그래서 왜 사용하는데?
내가 처음 게시한 블로그 글에 "단단한 컴포넌트 부수기"라는 내용을 언급을 했는데 인제 하나씩 알아가 보자고!
- 유연한 UI 구성
- 부모 컴포넌트의 상태와 로직을 기반으로 자식 컴포넌트를 조합하여 다양한 UI를 유연하게 설계할 수 있습니다
- 재사용성
- 로직과 UI를 분리해 여러 곳에서 재사용 가능하며, 다른 조합 방식으로 새로운 기능을 쉽게 추가할 수 있습니다.
- 컴포넌트 간의 명확한 역할 분리
- 부모는 상태와 로직을 관리하고, 자식은 UI를 정의해 역할이 명확해지고 코드 유지보수가 쉬워집니다.
- 확장성과 유지보수 용이성
- 추가 기능을 필요할 때 기존 구조를 변경하지 않고 새로운 자식 컴포넌트를 추가하기 쉽습니다.
자 아직도 어렵다고? 바로 코드 예시로 보여줄게!
// components -> Card.jsx
import React from "react";
export default function Card({text}) {
return (
<div className="flex flex-col gap-8 p-10 rounded-xl shadow-xl border-2">
<p>⭐⭐⭐⭐⭐</p>
<p>{text}</p>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Iure harum inventore, sunt accusamus suscipit distinctio minus maxime fuga sit perspiciatis? Eos eius ratione
corporis quod dolorem est sunt reprehenderit natus.
</p>
</div>
);
}
components -> CardList.jsx
import React from "react";
import Card from "./Card";
export default function CardList() {
return (
<div className="relative h-[800px] overflow-hidden grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 p-10">
<Card text=["1"} />
<Card text=["2"} />
<Card text=["3"} />
</div>
);
}
위 코드 토대로 위 이미지처럼 화면에 랜더링 되는 것을 확인할 수 있어~~!
자 그럼 내가 사용한 일반 컴포넌트의 문제점이 무엇이냐? 아주 쉽게 설명해 줄게 끝까지 읽어줘..!
- 구조의 강한 의존성
- Card 컴포넌트가 단일 목적으로 사용되고 있습니다. 컴포넌트가 유연하지 않아, 다른 용도로 사용하려면 Card를 수정하거나 별도의 컴포넌트를 작성해야 할 수 있습니다.
- 내부 요소의 커스터마이징 어려움
- Card가 어떤 방식으로 렌더링 될지는 Card 자체에 완전히 의존하므로, Card의 수정 없이 세부 사항을 변경할 수 없습니다.
이러한 이유 때문에 컴파운드 컴포넌트 패턴이 생겨난 이유야!
하나의 부모 컴포넌트와 여러 하위 컴포넌트를 조합하여 동작하는 패턴으로
이러한 패턴을 사용하면 내부 요소를 부모로부터 제어할 수 있으면서,
사용자의 의도에 따라 컴포넌트를 구성할 수 있어!!!
컴파운드 컴포넌트 사용 예시
//components -> Card.jsx
import React from "react";
export default function Card({ children }) {
return <div className="flex flex-col gap-8 p-10 rounded-xl shadow-xl border-2">{children}</div>;
}
function Star({ children }) {
return (
<div>
{/* 별점 */}
{children}
</div>
);
}
function Title({ children }) {
return (
<div className="font-bold text-3xl">
{/* 제목 */}
{children}
</div>
);
}
function Body({ children }) {
return (
<div>
{/* 내용 */}
{children}
</div>
);
}
Card.Star = Star;
Card.Title = Title;
Card.Body = Body;
- 잠깐만!!!??? Card는 컴포넌트 즉 함수인데 Card.Star 이런 보지도 못한 문법은 뭐야...!?
- 리액트에서 Card는 함수형 컴포넌트야 일단!
- 함수형 컴포넌트는 자바스크립트에서 함수로 정의되고 있어!
- 자바스크립트의 함수는 객체야~!
- 즉 객체는 메서드(멤버함수)를 사용할 수 있어서 객체에 접근하는 점(.) 표기법을 사용하여 해당 멤버함수 속성에 접근이 가능해!
- 리액트에서 Card는 함수형 컴포넌트야 일단!
import React from "react";
import Card from "./Card";
export default function CardList() {
return (
<div className="relative h-[800px] overflow-hidden grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 p-10">
<Card>
<Card.Star>⭐⭐⭐⭐</Card.Star>
<Card.Title>You can do it!!</Card.Title>
<Card.Body>이궈궈던!!!!!!!!이궈궈던!!!!!!!!이궈궈던!!!!!!!!</Card.Body>
</Card>
<Card>
<Card.Star>⭐⭐</Card.Star>
<Card.Title>You can do it!!22</Card.Title>
<Card.Body>이궈궈던!!!!!!!!이궈궈던</Card.Body>
</Card>
<Card>
<Card.Star>⭐⭐⭐⭐⭐⭐</Card.Star>
<Card.Title>You can do it!!3333</Card.Title>
<Card.Body>이궈궈던!!!!!!!!</Card.Body>
</Card>
<Card>
<Card.Star>⭐⭐⭐⭐</Card.Star>
<Card.Title>You can do it!!</Card.Title>
<Card.Body>이궈궈던!!!!!!!!이궈궈던!!!!!!!!이궈궈던!!!!!!!!</Card.Body>
</Card>
<Card>
<Card.Star>⭐⭐</Card.Star>
<Card.Title>You can do it!!22</Card.Title>
<Card.Body>이궈궈던!!!!!!!!이궈궈던</Card.Body>
</Card>
<Card>
<Card.Star>⭐⭐⭐⭐⭐⭐</Card.Star>
<Card.Title>You can do it!!3333</Card.Title>
<Card.Body>이궈궈던!!!!!!!!</Card.Body>
</Card>
</div>
);
}
- 아직 완벽하진 않아도 어떤 느낌인지는 알겠지...?
다른 개발자 왈 -
- 컴파운드 컴포넌트 사용 안 하고 props로 데이터 넘겨줘서 사용할 수 있는데용??
- props로 데이터 넘겨주는 게 구조가 더 단순하고 쉬워 보이는데...?
- 빠른 개발도 가능하는데 굳이...?
본인 왈 -
- 오케이~ 님 말도 다 맞아. 다만 간단한 요구사항일 때는 님 말이 맞지만 요구사항이 계속 늘어난다고 가정해 봐
- 이름, 성별, 나이, 가격, 취미, 키, 몸무게... 등등 이렇게 요구사항이 많아지면 props로 넘겨줘야 하는 데이터가 늘어나잖아
- 이런 걸 바로 props driling이라고 해!
- props driling의 문제점
- 코드 복잡성 증가
- props가 계속 증가하게 되면 유지보수의 어려움
- 재사용성 저하
- props의 의미를 파악하기 힘들어짐
- props driling의 문제점
- 그리고 나는 이름, 성별 <-- 이 2개만 사용하고 싶은데 props로 넘겨받은 불필요한 데이터가 생겨나잖아
※ 내가 하고 싶은 말이 정답이진 않지만 적재적소에 알맞게 사용하면 될 것 같다는 게 내 결론이야!
- 자 그러면 저 위에 반복되는 지저분한 코드를 리팩터링 해볼까!?
const dummy1 = [
{
id: 1,
star: "⭐⭐⭐⭐⭐",
title: "좋아요!",
content: "좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!",
},
{
id: 2,
star: "⭐⭐⭐⭐⭐⭐⭐⭐",
title: "좋아요!2",
content: "좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!",
},
{
id: 3,
star: "⭐⭐⭐⭐",
title: "좋아요!3",
content: "좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!",
},
{
id: 4,
star: "⭐⭐⭐",
title: "좋아요!4",
content: "좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!",
},
{
id: 5,
star: "⭐⭐⭐⭐",
title: "좋아요!5",
content: "좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!좋아요!",
},
];
import React from "react";
import Card from "./Card";
import { dummy1, dummy2, dummy3 } from "../data/cardData";
export default function CardList() {
console.log("dummy1", dummy1);
return (
<div className="relative h-[800px] overflow-hidden grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 p-10">
<div>
{dummy1.map((el) => (
<Card key={el.id}>
<Card.Star>{el.star}</Card.Star>
<Card.Title>{el.title}</Card.Title>
<Card.Body>{el.content}</Card.Body>
</Card>
))}
</div>
<div>
{dummy2.map((el) => (
<Card key={el.id}>
<Card.Star>{el.star}</Card.Star>
<Card.Title>{el.title}</Card.Title>
<Card.Body>{el.content}</Card.Body>
</Card>
))}
</div>
<div>
{dummy3.map((el) => (
<Card key={el.id}>
<Card.Star>{el.star}</Card.Star>
<Card.Title>{el.title}</Card.Title>
<Card.Body>{el.content}</Card.Body>
</Card>
))}
</div>
</div>
);
}
- 위 컴파운드 컴포넌트 패턴을 활용하여 적재적소에 알맞게 사용하면 내가 사용하고 싶은 컴포넌트만 사용해서 보여줄 수 있는 장점이 있겠지?
작업물
- tailwind + keyframes를 활용한 내용인데 이번 내용에서 animation 내용은 번외라 다음기회에....
느낀 점
- 무조건 컴파운드 컴포넌트를 사용하면 좋은 게 아닌 단순히 고정되어 있고 유지보수가 필요 없는 컴포넌트 같은 경우 일반 컴포넌트를 사용해서 가독성을 좋게 만드는 게 좋다는 게 내 생각이다.
- 컴파운드 컴포넌트 패턴을 사용함으로써 유지보수성이 증가가 될 것이며 커스텀이 자유롭게 꾸밀 수 있다는 게 내 생각이다.
- 컴파운드 컴포넌트를 초기에 개발 시 복잡해 보일 수 있겠지만, 규모가 커지고 요구사항이 늘어남에 따라 이 패턴의 효과가 상당할 것이라고 생각한다.