리액트의 Compound Pattern에 대해 알아보자

 

리액트에는 몇 가지 디자인 패턴들이 있습니다. 그 중에서 Compound Pattern에 대해 배워보도록 하겠습니다!

 

Compound 패턴을 통해 하나의 작업을 위해 여러 컴포넌트를 만들어 역할을 분담할 수 있게 됩니다. 

 

클릭하면 메뉴바를 보여줘서 수정, 삭제 작업을 할 수 있는 Flyout 컴포넌트를 만들어봅시다. 

`Flyout` 컴포넌트에는 2가지 컴포넌트가 필요합니다.

  1. 메뉴를 클릭하면 토글할 수 있도록 하는 `Toggle` 버튼
  2. 수정, 작업 버튼을 가지고 있는 `List` 컴포넌트

Context API를 통해 Compound Pattern을 구현해보도록 해보겠습니다.

 

먼저 Flyout 컴포넌트에 Context API의 Provider를 구현해보자.

type ContextType = {
  open: boolean;
  toggleOpen: Dispatch<SetStateAction<boolean>>;
};

const FlyoutContext = createContext<ContextType | null>(null);

function Flyout({ children }: PropsWithChildren) {
  const [open, toggleOpen] = useState(false);

  const providerValue = { open, toggleOpen };

  return (
    <FlyoutContext.Provider value={providerValue}>
      {children}
    </FlyoutContext.Provider>
  );
}

const useFlyoutContext = () => useContext(FlyoutContext);

 

이제 Flyout Context와 Compound pattern을 활용하여 Menu를 구현해보도록 합시다.

Flyout 컴포넌트의 Statci Property로 Menu 내부에 사용될 컴포넌트를 할당하는 패턴을 사용합니다.

function Toggle() {
  const { open, toggleOpen } = useFlyoutContext()!;
  return (
    <button onClick={() => toggleOpen(!open)}>{open ? "Close" : "Open"}</button>
  );
}

function List({ children }: PropsWithChildren) {
  const { open } = useFlyoutContext()!;
  return open ? <ul>{children}</ul> : null;
}

function Item({ children }: PropsWithChildren) {
  return <li>{children}</li>;
}

Flyout.Toggle = Toggle;
Flyout.List = List;
Flyout.Item = Item;

 

이제 이러한 것들을 사용해서 FlyoutMenu 컴포넌트를 만들어봅시다.

 

export default function FlyoutMenu() {
  return (
    <Flyout>
      <Flyout.Toggle />
      <Flyout.List>
        <Flyout.Item>Item 1</Flyout.Item>
        <Flyout.Item>Item 2</Flyout.Item>
        <Flyout.Item>Item 3</Flyout.Item>
      </Flyout.List>
    </Flyout>
  );
}

 

이렇게 된다면 FlyoutMenu 컴포넌트 자체에는 아무런 상태를 가지고 있지 않습니다. (이 패턴은 컴포넌트 관련 라이브러리를 만들 때 유용하다고 합니다.)

 

이렇게 함으로써 장점은 무엇이 있을까요?

 

1) Props를 좀 더 깔끔하게 넘길 수 있게 됩니다. (props drilling 문제도 해결할 수 있습니다.) 

// 이전 코드
<MediumClap
  onClap={handleClap}
  handleCount={handleCount}
  updateTotal={updateTotal}
  count={count}
  total={total}
/>

// Compound Pattern을 사용한 코드
<MediumClap onClap={handleClap}>
  <MediumClap.Icon />
  <MediumClap.Count count={count} handleCount={handleCount} />
  <MediumClap.Total total={total} updateTotal={updateTotal} />	
</MediumClap>

2) UI를 유연하게 변경할 수 있습니다.

children에 컴포넌트를 넣기 때문에, 컴포넌트의 순서를 조정하면 UI를 변경할 수 있게 됩니다.

3) 관심사의 분리

비즈니스 로직은 부모 컴포넌트에만 가지고 있게 되며, 이렇게 작성함으로써 다른 컴포넌트에서도 재사용될 확률이 높아집니다.
4) 가독성을 높일 수 있습니다.

사용하는 쪽만 보고서도 컴포넌트 내부가 어떻게 될지 예상할 수 있다는 장점이 있습니다.

 

이러한 패턴을 활용함으로써 IoC(Inversion of Control)을 활용할 수 있습니다.

예시를 보면 `Flyout` 부모 컴포넌트가 자식 컴포넌트들을 통제하고 있습니다. 코드를 사용하는 클라이언트 입장에서는 컴포넌트들에 대한 통제권을 잃어버리고 컨테이너/프레임워크(우리의 경우, `Flyout` 컴포넌트)에게 제어권을 넘겨줌으로 제어의 역전이 달성됩니다.

 

 

 

 

출처

https://velog.io/@yesbb/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EC%9D%98-%EA%B4%80%EC%A0%90%EC%9C%BC%EB%A1%9C-%EB%B0%94%EB%9D%BC%EB%B3%B8-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EA%B3%A0%EA%B8%89-%ED%8C%A8%ED%84%B4-Compound-component-Render-props#%EA%B5%AC%ED%98%84%EB%B0%A9%EB%B2%95-

 

객체지향으로 리액트 고급 패턴 이해하기[Compound component & Render props]

리액트의 고급 패턴인 Compound Component 패턴과 Render Prop 패턴을 이해 및 적용하고, 이것들을 객체지향적 관점으로 조망해보겠습니다

velog.io

https://patterns-dev-kr.github.io/design-patterns/compound-pattern/

 

Compound 패턴

하나의 작업을 위해 여러 컴포넌트를 만들어 역할을 분담하게 한다 - …

patterns-dev-kr.github.io