본문 바로가기

[리액트] Compound Component 활용하기

DEV 커뮤니티에서 React Component Patterns에 관한 글에서 Compound 컴포넌트 패턴을 소개하는 글을 읽고 실무에 적용해보았다.

 

글에서 소개하는  Compound Component에 대해 간단하게 설명하자면 내부 state를 공유하며 서로 상호작용하는 컴포넌트들을 내부 static 컴포넌트로 두어 사용자에게 내부 로직은 추상화하고, 이외의 로직 작성을 독립적으로 작성할 수 있게 해주는 패턴이다.

 

"Compound components is a pattern where components are used together such that they share an implicit state that lets them communicate with each other in the background."

 

말이 좀 어려운거 같지만 쉽게 생각하면 <select> 태그는 지 혼자서는 아무 역할도 안하지만 내부 엘리먼트로 <option> 엘리먼트들을 작성하여 선택상자라는 역할을 완성할 수 있다.

 

아래는 글쓴이가 예시로 구현한 이미지 선택 예시코드이다. RadioImageForm을 사용하는 컴포넌트는 RadioImageForm의 내부 로직과 독립적으로 모듈을 작성할 수 있는 동시에 RadioInput은 별도의 컴포넌트가 아니라 RadioImageForm의 SubComponent라는 것을 명시적으로 알 수 있다.

 

 

Compound Component는 트리쉐이킹이 불가능하기 때문에 조건에 따라 변경되는 컴포넌트에는 부적합하며, 내부 State를 공유하기 위해 Provider Pattern을 활용한다.

 

Compound Component 패턴을 적용하여 약관 동의 페이지를 리팩토링하였다. 기존에는 독립적으로 존재하던 체크박스아이템과 내부 SubList 컴포넌트를 AgreementList에 SubComponent로 두었다.

 

한가지 고민인 건 AgreementList의 children에 아래 코드를 약관1, 약관2가 리스트만 다르고 구조는 서로 동일한데

<div className={cx("area-agree")}>
  <strong className={cx("name")}>약관 1</strong>
    <ul className={cx("list-check")}>
    {a1List.map((item, key) => (
      <AgreementList.Item key={key} item={item} />
    ))}
  </ul>
</div>

처음에 의도한 건 Page 컴포넌트에서 전체 화면 구조를 파악하기 쉽게 하고 싶었기 때문에 일부러 따로 분리하지 않았다.

(약관동의 화면이 제목과 아래에 목록이 열거되는 구조라는 걸 가시적으로 두고 싶었음)

Page 컴포넌트의 구조

Child 컴포넌트로 분리하면 이 구조를 Page 컴포넌트에서 한번에 파악하기 어려우니 한 단계 줄이려고 이렇게 두었는데 분리하는 장점이 있다면 분리해도 좋을거 같다 🧐

 


 

2020.10.07

React Hooks와 Context를 이용한 설계 패턴을 읽고 AgreementList에서 작성한 로직을 useAgreement 훅으로 분리하였다. 이렇게 하면 약관 동의 Context 사용이 필요할 때마다 useAgreement Hook을 호출하여 코드 재사용성을 높일 수 있다.

 

2022.02.24

오랜만에 patterns.dev 번역하다가 발견해서 다시 읽어보니 React.Children.map 으로 children 컴포넌트에 props를 주입하는 것 또한 Compound component 에 해당한다.

// checkStatus는 로컬 함수
const checkStatus = async () => {
    try {
        const {status, nfMngNum, lcnsRegNum} = await homeStore.checkInfo()
        nfMngNumStore.setNfMngNum(nfMngNum)
        lcnsRegNumStore.setLcnsRegNum(lcnsRegNum)
        setExistStatus(status)
    } catch (error) {
        throwError(error)
    }
}

const childrenWithProps = React.Children.map(children, (child, index) => {
    return React.cloneElement(child, {
        checkStatus, // props로 주입
        index,
    })
})