frontend/디자인 패턴

[REACT] 컴파운드(조합) 컴포넌트

프론트엔드코린이 2025. 1. 21. 14:27

컴파운드 컴포넌트란?

  1. 하나의 큰 컴포넌트(자이언트 컴포넌트)를 세부 요소로 쪼개놓고, Root에 assign 하는 형태합성하는 것입니다.
  2. prop을 사용하지 않고 내부에서 데이터를 처리할 수 있고, 비슷한 디자인의 컴포넌트가 필요할 때 새로운 컴포넌트를 만들지 않고 사용할 수 있습니다.

 

컴파운드 컴포넌트...?

  • 컴파운드 컴포넌트?? 용어부터 어렵잖아....?

 

그래서 왜 사용하는데?

내가 처음 게시한 블로그 글에 "단단한 컴포넌트 부수기"라는 내용을 언급을 했는데 인제 하나씩 알아가 보자고!

  1. 유연한 UI 구성
    • 부모 컴포넌트의 상태와 로직을 기반으로 자식 컴포넌트를 조합하여 다양한 UI를 유연하게 설계할 수 있습니다
  2. 재사용성
    • 로직과 UI를 분리해 여러 곳에서 재사용 가능하며, 다른 조합 방식으로 새로운 기능을 쉽게 추가할 수 있습니다.
  3. 컴포넌트 간의 명확한 역할 분리
    • 부모는 상태와 로직을 관리하고, 자식은 UI를 정의해 역할이 명확해지고 코드 유지보수가 쉬워집니다.
  4. 확장성과 유지보수 용이성
    • 추가 기능을 필요할 때 기존 구조를 변경하지 않고 새로운 자식 컴포넌트를 추가하기 쉽습니다.

 

 

말하는 감자는 너무 어렵다고...ㅠㅠ

 

자 아직도 어렵다고? 바로 코드 예시로 보여줄게!

// 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>
  );
}

 

위 코드 토대로 위 이미지처럼 화면에 랜더링 되는 것을 확인할 수 있어~~!

 

 

자 그럼 내가 사용한 일반 컴포넌트의 문제점이 무엇이냐? 아주 쉽게 설명해 줄게 끝까지 읽어줘..!

  1. 구조의 강한 의존성
    • Card 컴포넌트가 단일 목적으로 사용되고 있습니다. 컴포넌트가 유연하지 않아, 다른 용도로 사용하려면 Card를 수정하거나 별도의 컴포넌트를 작성해야 할 수 있습니다.
  2. 내부 요소의 커스터마이징 어려움
    • 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는 함수형 컴포넌트야 일단!
      1. 함수형 컴포넌트는 자바스크립트에서 함수로 정의되고 있어!
      2. 자바스크립트의 함수는 객체야~!
      3. 즉 객체는 메서드(멤버함수)를 사용할 수 있어서 객체에 접근하는 점(.) 표기법을 사용하여 해당 멤버함수 속성에 접근이 가능해!
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>
  );
}

위 코드 기반 렌더링 화면이야!

  • 아직 완벽하진 않아도 어떤 느낌인지는 알겠지...?

 

 

다른 개발자 왈 -

  1. 컴파운드 컴포넌트 사용 안 하고 props로 데이터 넘겨줘서 사용할 수 있는데용??
  2. props로 데이터 넘겨주는 게 구조가 더 단순하고 쉬워 보이는데...?
  3. 빠른 개발도 가능하는데 굳이...?

본인 왈 -

  1. 오케이~ 님 말도 다 맞아. 다만 간단한 요구사항일 때는 님 말이 맞지만 요구사항이 계속 늘어난다고 가정해 봐
  2. 이름, 성별, 나이, 가격, 취미, 키, 몸무게... 등등 이렇게 요구사항이 많아지면 props로 넘겨줘야 하는 데이터가 늘어나잖아
  3. 이런 걸 바로 props driling이라고 해!
    • props driling의 문제점
      1. 코드 복잡성 증가
      2. props가 계속 증가하게 되면 유지보수의 어려움
      3. 재사용성 저하
      4. props의 의미를 파악하기 힘들어짐
  4. 그리고 나는 이름, 성별 <-- 이 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 내용은 번외라 다음기회에....

잘 작동된다.....!

느낀 점

  1. 무조건 컴파운드 컴포넌트를 사용하면 좋은 게 아닌 단순히 고정되어 있고 유지보수가 필요 없는 컴포넌트 같은 경우 일반 컴포넌트를 사용해서 가독성을 좋게 만드는 게 좋다는 게 내 생각이다.
  2. 컴파운드 컴포넌트 패턴을 사용함으로써 유지보수성이 증가가 될 것이며 커스텀이 자유롭게 꾸밀 수 있다는 게 내 생각이다.
  3. 컴파운드 컴포넌트를 초기에 개발 시 복잡해 보일 수 있겠지만, 규모가 커지고 요구사항이 늘어남에 따라 이 패턴의 효과가 상당할 것이라고 생각한다.