본문 바로가기

FrontEnd

[React 디자인 패턴] Renderless Component 패턴

반응형

리액트에는 훅 아키텍처 이전에 유행한 Render Props 패턴이 있었다.

https://reactjs.org/docs/render-props.html

 

Render Props – React

A JavaScript library for building user interfaces

reactjs.org

children을 함수 형태 인자로 받아 하단 컴포넌트로의 의존성을 명시적으로 제공하는 패턴으로

의존성 주입의 베스트 프랙티스 중 하나로 여겨졌지만, 반대로 render props hell을 생성하는 단점이 있었다.

 

훅 아키텍처와 함께 사라진 패턴이지만, 분명 컨테이너 컴포넌트는 사이드 이펙트의 격리를 위해 필요하다.

https://kciter.so/posts/effective-atomic-design

 

Effective Atomic Design

소프트웨어 개발 중 설계에서 가장 중요한 것은 모듈화와 추상화 두 가지라고 할 수 있다. 웹 프론트엔드 업계는 이미 React, Vue.js, Angular와 같은 오픈소스 프레임워크를 통해 끝을 달리는 추상화

kciter.so

추상화의 단위를 어떻게 가져갈 것인가에 대해선 이견이 있지만,

프리젠테이션과 사이드 이펙트의 관심사가 섞이면 재앙이 발생한다는 것은 이견이 없다.


Vue3의 Renderless component

tailwind css의 창시자인 adam wathan이 작성한 글로 실제 사용사례를 확인하자.

리액트 쿼리 useQuery 훅을 포함한 컴포넌트에 url을 파라미터로 넘기는 것과 유사하다.

<MouseTracker v-slot="{ x, y }">
  Mouse is at: {{ x }}, {{ y }}
</MouseTracker>

위 예제는 마우스 커서의 포지션을 자식 컴포넌트 prop으로 받는 형태로,

render props 패턴과 달리 함수 컴포넌트를 전달하지 않아도 된다.

 

Vue3은 React와 달리 emit을 통해 부모 컴포넌트가 리스닝 할 수 있는 이벤트와

위와 같이 slot에 전달될 수 있는 props을 API 명세로 한다.

따라서 부모 컴포넌트에서 자식 컴포넌트의 상태를 전달받아 사용할 수 있다.

해당 상태는 원래 슬롯의 props로 전달될 녀석이었던 것이다.

 

이를 숙지하고 리액트에서 이와 유사한 구현을 시도해보자


React의 Renderless component

리액트에서는 Vue3처럼 자식 컴포넌트의 데이터를 부모에서 가져오는 방법이 없기 때문에,

사용처에서는 다음과 같이 보여야 할 것이다.

  <MouseTracker>
    <MyElement />
  </MouseTracker>

해당 패턴을 사용하려면 두 가지가 필요하다.

 

1. 자식 컴포넌트는 x,y를 props으로 받아 사용하며, default 값을 제공해야 한다(required=false).

const MyElement = ({ x, y }) => (
  <>
    <span>x : {x}</span>
    <br />
    <span>y : {y}</span>
  </>
);

물론 해당 default 값은 부모에서 덮어쓸 수 있으나, 이래야 타입스크립트 사용 시 오류가 안난다.

required=false 조건을 위해서라고 생각하자

2. 부모 컴포넌트는 자식에게 해당 속성을 암시적으로 전달한다.

물론 x,y를 잘못 오버라이트 하면 문제가 발생할 수도 있다.

const MouseTracker = ({ children }) => {
  const [x, y, bind] = useMousePosition();
  const clonedChildren = React.cloneElement(children, {
    x,
    y
  });

  return (
    <div
      style={{ width: "100%", height: "500px", border: "2px solid gray" }}
      {...bind}
    >
      {clonedChildren}
    </div>
  );
};

마치며

리액트 쿼리와 같은 유틸리티 훅 라이브러리를 사용하다 보면, 지루한 보일러 플레이트를 반복 작성하게 되는 일이 왕왕 있다.

상태가 있는 로직(사이드이펙트) 처리와 렌더링을 동시에 할 일이 있다면, 해당 패턴을 연구해보자.

 

React.Children과 함께 사용하여 모든 자식 컴포넌트에 props를 전달할 수도 있다.

(지금 구조로는 배열로 children이 전달될 경우 생각한 대로 동작하지 않을 것이다.)

 

참고로 꽤 논란이 있을 수 있는 패턴이니 팀의 다른 개발자와 충분한 상의 후 사용하자.

 

반응형