합성컴포넌트(Composite Component)

김민기·2023년 7월 31일
3

합성컴포넌트란?

합성 컴포넌트란 소프트웨어 개발에서 재사용이 가능한 구성 요소를 만들기 위해 여러 개의 다른 컴포넌트를 조합하는 디자인 패턴이다. 복합적인 기능을 가진 큰 컴포넌트를 작은 단위의 컴포넌트들로 구성함으로써 코드의 유지보수성과 확장성을 높일 수 있다.

합성 컴포넌트 패턴에서는 컴포넌트들이 계층 구조로 구성된다. 각 컴포넌트는 하위 컴포넌트들을 가지며, 최상위 컴포넌트는 하위 컴포넌트들의 조합으로 이루어진다. 이로 인해 단일 컴포넌트를 사용하는 것보다 더 복잡하고 다양한 기능을 제공할 수 있다.

예를 들어, 그래픽 사용자 인터페이스(GUI)를 개발하는 경우, 버튼, 텍스트 상자, 체크박스 등의 작은 컴포넌트들을 만들어두고, 이러한 작은 컴포넌트들을 조합하여 더 큰 형태의 윈도우 또는 팝업 컴포넌트를 만들 수 있다. 이렇게 함으로써 유사한 기능을 가진 컴포넌트들을 중복으로 작성할 필요 없이 재사용성이 증가하고 코드의 가독성이 높아진다.

하나의 컴포넌트를 여러 가지 집합체로 분리한 뒤, 분리된 각 컴포넌트를 사용하는 쪽에서 조합해 사용하는 컴포넌트 패턴을 의미한다.

<select>
	<option value="1">Option 1</option>
	<option value="2">Option 2</option>
</select>

간단한 예시로 html의 select를 볼 수 있는데, select는 select와 option 태그의 조합으로 이루진다.

select와 option은 각각 독립적으로는 큰 의미가 없지만 사용하는 곳에서 이를 조합해 사용함으로써 화면에 의미 있는 요소가 된다.

1. 합성컴포넌트는 UI와 로직을 분리시키는데 유용하게 사용

UI와 로직을 분리하는 것은 소프트웨어 개발에서 중요한 원칙 중 하나인 "단일 책임 원칙"을 따르기 위한 접근 방법이다. 합성 컴포넌트 패턴은 이를 구현하는 데 도움이 되며, 이를 통해 컴포넌트들 간의 의존성을 줄이고 독립성을 강화할 수 있다.

  1. UI 관점
  • 합성 컴포넌트는 여러 작은 컴포넌트들로 구성된다. 각 작은 컴포넌트는 UI에 대한 표현을 담당한다. 예를 들어, 버튼, 텍스트, 이미지 등을 담당하는 작은 컴포넌트들이 UI 구성을 형성한다.
  • 이렇게 구성된 작은 컴포넌트들은 다른 컴포넌트에서 재사용 가능하다. UI를 구성하는 작은 단위들로 나누어 작성하므로, 코드의 가독성과 재사용성이 높아진다.
  1. 로직 관점
  • 합성 컴포넌트는 작은 컴포넌트들을 조합하여 더 큰 컴포넌트를 형성하는데 사용된다. 이 때 각 작은 컴포넌트의 로직이 분리됩니다. 예를 들어, 작은 컴포넌트들은 데이터를 가지고 있지 않으며, 데이터는 부모 컴포넌트에서 받아온다.
  • 작은 컴포넌트들은 상태(state)를 가지지 않으며, 주로 부모 컴포넌트에서 전달받은 프로퍼티(props)에 따라 UI를 렌더링한다. 이로 인해 컴포넌트 간의 결합도가 낮아지고 관리가 용이해진다.

사용해보기

<Counter title="카운터" initValue={0} minimum={0} maximum={100} />

이러한 Counter 컴포넌트가 있다. 이 Counter 컴포넌트를 합성컴포넌트로 리팩토링 해보았다.

이런식으로 로직과 UI를 분리하여 합성컴포넌트로 구현할 수 있다.

1. Counter.tsx

import React, { createContext, useContext, useState } from "react";
import CounterButton from "./pages/UI/Button";
import CounterTitle from "./pages/UI/Title";
import CounterStatus from "./pages/UI/CounterStatus";

interface Props {
  children: React.ReactNode;
  initValue: number;
  minimum?: number;
  maximum?: number;
}

const compositeContext = createContext({
  counter: 0,
  counterPlus: () => {},
  counterMinus: () => {},
});

export const Counter = ({ children, initValue, minimum, maximum }: Props) => {
  const [counter, setCounter] = useState(initValue);

  const counterPlus = () => {
    setCounter((prev) => {
      if (maximum === undefined) {
        return prev + 1;
      } else {
        return prev < maximum ? prev + 1 : prev;
      }
    });
  };

  const counterMinus = () => {
    setCounter((prev) => {
      if (minimum === undefined) {
        return prev - 1;
      } else {
        return prev > minimum ? prev - 1 : prev;
      }
    });
  };

  return (
    <compositeContext.Provider value={{ counter, counterPlus, counterMinus }}>
      {children}
    </compositeContext.Provider>
  );
};

Counter.Title = CounterTitle;
Counter.Button = CounterButton;
Counter.Status = CounterStatus;

export const useCounter = () => useContext(compositeContext);

ContextApi를 사용하여 상태와 로직들을 중앙에서 관리하고 UI와 분리시켰다.

2. Button.tsx

import { useCounter } from "../../CompositContext";

interface Props {
  children: React.ReactNode;
  type: "increment" | "decrement";
}

export default function CounterButton({ children, type }: Props) {
  const { counterPlus, counterMinus } = useCounter();
  return (
    <button onClick={type === "increment" ? counterPlus : counterMinus}>
      {children}
    </button>
  );
}

3.CounterStatus.tsx

import { useCounter } from "../../CompositContext";

export default function CounterStatus() {
  const { counter } = useCounter();
  return <p>{counter}</p>;
}

3.Title

interface Props {
  children: React.ReactNode;
}

export default function CounterTitle({ children }: Props) {
  return <h2>{children}</h2>;
}

0개의 댓글

관련 채용 정보