본문 바로가기

FrontEnd

리액트 디자인 패턴 : uncontrolled compound components(제어없는 컴파운드 컴포넌트)

반응형

데이터가 서로 관련 있는 컴포넌트들 끼리의 colocation은 가독성과 유지보수성 향상, 최적화 효과를 가져옵니다.
(이를 위해 Portal을 응용합니다.)
compound component 패턴은 더 유연한 컴포넌트를 제공할 수 있게 해줍니다.
uncontrolled component는 API 사용자의 상태관리 부담을 제거하여, 유지보수성과 가독성을 높여줍니다.
이 세가지를 효과적으로 사용해 봅시다.
먼저 아래 게시글을 읽어주세요
https://itchallenger.tistory.com/755

 

컴파운드 컴포넌트 잘만들기 3탄 : compound component + uncontrolled component + co-location 삼신기 사용하기

컴파운드 컴포넌트 + uncontrolled component + co-location 삼신기로, API 컨슈머의 상태 관리 부담을 덜어줄 수 있습니다. 또한 전역 상태도 피할 수 있습니다. 원문 번역입니다 : https://jjenzz.com/avoid-glo..

itchallenger.tistory.com

TL;DR :

uncontrolled compound component 패턴을 이용하면 컴포넌트의 렌더링 위치와 Actual Dom의 위치를 분리하여,
데이터와 의존성 중심으로 컴포넌트들을 묶어낼 수 있습니다.


먼저 동작하는 허접한 스케치 클론을 보고 가시죠.
위 게시물 원 저자의 결과물을 포크해서 약간 수정했습니다.
아래 결과물은 오직 컨텍스트API만 사용합니다.

위 앱의 기능은 다음과 같습니다.


1. Add rectangle로 상자를 추가할 수 있습니다.
2. 상자를 클릭하면 우측 사이드바에 색상 변경 패넡이 나타납니다. 이용하여 스타일을 변경할 수 있습니다.
3. 상자를 클릭하면 좌측 상단에 삭제 버튼이 나타납니다. 해당 버튼을 클릭하면 삭제됩니다.

뭔가 생각보다 컴포넌트와 상태 구조가 복잡해 보입니다만, 컴포넌트는 아래와 같이 단순(?)합니다.
전역 상태는 전혀 사용하지 않으며, 하위 컨텍스트는 항상 상위 컨텍스트에 의존하는 단방향 구조입니다.

App.js

const AddRectangle = () => (
  <Rectangle>
    <AddRectangleTrigger>Add rectangle</AddRectangleTrigger>
    <MainPortal>
      <RectangleShape>
        <SidebarPortal>
          <RectangleStyler />
        </SidebarPortal>
        <NavbarPortal>
          <DeleteRectangleTrigger>Delete rectangle</DeleteRectangleTrigger>
        </NavbarPortal>
      </RectangleShape>
    </MainPortal>
  </Rectangle>
);

const App = () => (
  <Layout>
    <LayoutNav>
      <AddRectangle />
    </LayoutNav>
    <LayoutMain />
    <LayoutSidebar />
  </Layout>
);

최상단의 RectangleContext는 상자의 갯수, id 관리에 관여합니다.
reducer와 dispatch를 통해 상자 삭제, 추가 로직도 제공합니다.
AddRectangle을 클릭하면, id 리스트를 이용해 각 컴포넌트를 렌더링하며, RectangleContext의 상자 리스트에 id를 추가합니다.
삭제 시 해당 리스트에서 id를 삭제하는 것으로, 상자를 제거할 수 있습니다.

RectangleShapeContext는 상자의 비주얼에 관련한 모든 것을 담당합니다.
선택 여부도 해당 컨텍스트 안에 캡슐화 되어 있습니다.
이는 RectangleShape의 컨텍스트(selected)를 사용합니다.
상자 선택 취소는 상자와 사이드바 스타일러 바깥을 클릭하면 발생합니다.
해당 로직 또한 상자 안에 캡슐화 되어 있습니다.
즉, 상자는 삭제를 스스로 책임집니다.

또한 삭제 버튼 등장 여부 또한 RectangleShape의 컨텍스트 (selected)를 사용합니다.
버튼의 렌더링 위치는 포탈을 이용해 Navbar로 옮겨지며,
RectangleShape가 렌더링 되어야 포탈에 버튼이 렌더링 된다는 것을 컴포넌트 트리 구조를 통해 알 수 있습니다.
즉, 데이터 변경 책임이 있는 컴포넌트의 상테를 가져와서 사용하며,
관련있는 컴포넌트들을 컴포넌트 트리에서 가까이 위치시킬 수 있습니다.
이는 colocation 측면에서 유지보수의 이점을 가져오며, 트리 셰이킹 가능성과 컴포넌트의 독립성을 부여합니다.


결론 : 렌더링 위치와 Actual Dom 상의 위치를 분리하기

UI를 개발하면서 지금까지 가장 어려웠던 것은 실제 돔의 위치를 고려하여 컴포넌트를 단계적으로 배치해야 했던 것이 아닐까 싶습니다.
이는 상태 배치를 특히 더 어렵게 합니다. 위에 렌더링 되는 컴포넌트는 반드시 위에 있어야 하기 떄문이죠.
uncontrolled compound component 패턴을 이용하면 컴포넌트의 렌더링 위치와 Actual Dom의 위치를 분리하여,
데이터 중심으로 컴포넌트들을 묶어낼 수 있습니다.
컴포넌트 트리의 계층구조는 렌더링 결과 Virtual Dom의 Actual Dom에서의 위치와와 독립적으로 ,
자식 렌더링의 전제조건과 데이터 의존성 구조를 나타내게 됩니다.
이는 데이터 변경 책임 중심으로 컴포넌트들을 묶어낼 수 있음을 의미합니다.
데이터 플로우를 로컬라이징해서 쉽고 간단하게 파악할 수 있습니다.

리액트는 UI와 데이터 흐름과의 동기화에 관한 것입니다.

댄 아브라모프 : a-complete-guide-to-useeffect/

 

useEffect 완벽 가이드

이펙트는 데이터 흐름의 한 부분입니다.

overreacted.io

저도 이 패턴을 이해한지 얼마 안되었고, 실제 업무에서 사용사례를 찾고 있습니다. (사실 어제 처음 봤습니다.)
언뜻 보기에 이상해보이는 패턴이긴 하나,
실제 디자인 프로덕트를 개발하던 저자의 idea니 완전 말이 안된다고 생각하지도 않습니다. (Radix UI 팀의 개발자)
또 저도 이 글을 읽는 여러분이 어떤 부분이 어렵고 말이 안된다 생각하는지 궁금합니다.
댓글로 의견주시면 감사하겠습니다.

반응형