Compound Pattern

김동하·2023년 3월 10일
3

react

목록 보기
23/31

들어가며

요새 컴포넌트 패턴을 공부하는데 어렵다. 왜 어렵냐면 그간 써왔던 방법이 아니라, 익숙하지 않은 느낌...? children으로 렌더링을 하다 보니까 내부적으로 돌아가는 게 한 번에 확 이해가 되지 않는 것도 있다.

그런데 조금만 배우면 정말 정말 이게 좋다는 걸 느낄 수 있다.. 진짜 실무에서 UI 컴포넌트 디자인 조금만 달라지거나 기능 추가 되어도 props로 내리거나 파일 하나 만들어서 복붙하거나 그랬는데, 그래서 시간이 지나면 드러워지고 뭐가 뭔지 모르고 'is...' 이런 boolean 값 천지였는데, 컴파운드는 정말 이런 문젤 효과적으로 처리할 수 있다.

우리 친애하는 kentcdodds 선생님이 아주 좋아하는 (컴파운드를 소개할 수 있어 익사이팅하다고 하셨다) 컴파운드 컴포넌트를 알아보장

컴파운드 패턴

컴파운드 패턴은 하위 컴포넌트를 조합하여 만드는 패턴이다. 리액트 자체가 컴포넌트 조합이라 이게 왜 '패턴'이라는 이름이 붙는가 싶어 처음에 조금 헷갈리지만 잘 살펴보면 다음과 같은 특징을 갖는다.

  • 하나의 부모 컴포넌트가 있고 여러 하위 컴포넌트가 있다.
  • 부모와 각 하위 컴포넌트는 그 자체로는 존재할 수 없고 서로 조합을 해야만 한다.
  • 하위 컴포넌트는 props.children 속성을 통해 관리한다.
  • 로직을 내부로 숨길 수 있다.
  • 유지 보수/확장/가독성이 매우 매우 좋다!!

컴파운드 컴포넌트의 가장 대표적인 예제로 select, option 태그가 있는데 이 두 태그는 독립적으로는 의미가 없고 같이 써야만 하나의 기능을 할 수 있다.

내가 생각했을 때 가장 좋은 점은 계층이 깊어질수록 진가가 발휘되고 관심사 분리가 되고 props를 내릴 경우 필요한 컴포넌트한테 직접 줄 수 있다.

예제

평범한 토글 버튼이 있다. 토글의 on/off에 따라 text를 다르게 보여준다고 했을 때 이걸 컴파운드로 만들어보자

<Toggle> 컴포넌트에서 boolean을 상태값으로 관리하고 하위 컴포넌트에게 상태를 props로 내려 UI를 업데이트 하는 것이다.

<ToggleButton>은 input으로 이벤트아 boolean값을 props로 받는다.

즉, boolean 값은 하위 컴포넌트들 모두와 공유 되어야 하고 boolean 값을 업데이트 하는 이벤트는 <ToggleButton>와 공유되어야 한다.

<ToggleOn><ToggleOff> 는 on/off에 따라 children을 return 한다.

그럼 이제 부모, <Toggle> 가 하위 컴포넌트들에게 props를 어떻게 줄 것이냐!?

React.Children을 사용하여 props로 받은 children을 맵핑한다.

console.log 찍어보면 아래와 같이 3개의 컴포넌트가 children으로 왔음을 확인할 수 있다.

이제 부모의 상태값 ontoggle 을 전달해주어야 하는데 어떻게 전달할 수 있을까

이렇게 하면 에러가 나올 텐데 이유는 props를 직접 변경하는 건 불가능하기 때문에. 그래서 cloneElement 을 사용해야 한다. 말그대로 element를 복제한다.

이렇게 수정하고 다시 children 을 살펴보면

props 프러퍼티에 on과 toggle이 들어갔다.

on/off에 달라지는 toggle 이다.

내부 로직은 숨겼기 때문에 코드가 깔끔하다 (= 다른 코드들도 많이 있었다면 피로도가 엄청 줄어들 것!)

further

사실 컴파운드의 장점은 이제부터다.

만약 이 상태에서 어떤 조건에서만 UI(혹은 기능)을 추가한다고 가정해보자. 아래는 실제 프로젝트에서 사용했던 토글 버튼이다.

공통으로 사용되기 때문에 props로 분기를 해줘야 하는데 아래와 같이 변할 것이다. 이렇게 되면 시간이 지나면 지날수록 가독성이나 유지보수가 힘들어진다. (어떤 경우 어떤 UI 혹 기능이 되는지 찾는데 리소스가 너무 든다.. 나는 그랬다...)

하지만 컴파운드는 조합 + props.chidren 이기 때문에 그냥 children만 내려주면 된다.

외부에서 보기에도 훨씬 직관적이고 어떤 UI인지 짐작하기 쉽다.

또한, 컴포넌트를 추가하면서 on, toggle을 그대로 사용할 수 있다. (상속이라고 해야 하나 확장이라고 해야 하나)

비슷한 기능을 하는<MyToggelButton>을 추가한다고 가정했을 때 children로 꽂아주면

짜잔

further2

또 가정해보자. 만약 하위 컴포넌트가 직접 props를 받을 수 있는 상황이 아니라면 어떻게 할까.

컴파운드가 유지 보수에 용이한 건 style도 외부에서 주입할 수 있어서인데 만약 다른 스타일을 주입했다고 가정하자.

그럼 <div>으로 인해 부모로 부터 props 받지 못하게 된다. 사실 컴포넌트를 만든다고 하면 이런 경우가 대부분이다.

이럴 때 context api를 사용한다.

Toggle 컴포넌트 내부에서 공유할 상태값을 Provider로 내려준다.

가장 상위 컴포넌트에서 Provider로 감싼다.

그리고 각 필요한 컴포넌트에서 가져다 사용하면 된다. 이제 뎁스가 얼마나 되든 props를 바로 전달해줄 수 있게 되었다.

짜잔2

** Provider에서 내려주는 value에 useMemo로 최적화를 할 수 있다.

참고 - https://kentcdodds.com/blog/compound-components-with-react-hooks

profile
프론트엔드 개발

0개의 댓글