리액트에는 몇 가지 디자인 패턴들이 있습니다. 그 중에서 Compound Pattern에 대해 배워보도록 하겠습니다!
Compound 패턴을 통해 하나의 작업을 위해 여러 컴포넌트를 만들어 역할을 분담할 수 있게 됩니다.
클릭하면 메뉴바를 보여줘서 수정, 삭제 작업을 할 수 있는 Flyout 컴포넌트를 만들어봅시다.
`Flyout` 컴포넌트에는 2가지 컴포넌트가 필요합니다.
- 메뉴를 클릭하면 토글할 수 있도록 하는 `Toggle` 버튼
- 수정, 작업 버튼을 가지고 있는 `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-
https://patterns-dev-kr.github.io/design-patterns/compound-pattern/