본문 바로가기
펀잇

compound component pattern을 활용해 컴포넌트 조립하기

by 해-온 2024. 8. 20.

 

 

'펀잇'에서 디자인 마이그레이션 작업을 진행하면서, 

약간씩 다른 컴포넌트를 구현해야 할 일이 생겼다.

 

위 사진은 꿀조합에 대한 정보를 요약해서 보여주는 컴포넌트로

많은 페이지의 이곳저곳에서 사용되고 있다.

 

사실상 같은 정보를 내려받는 컴포넌트인데,

디자인이 4가지로 나누어져서

 이를 모두 구현해야 할 필요가 생겼다.

 

예전에는 디자이너분들이 없기도 했고,

해봤자 2가지 정도의 시안으로 나누어졌기 때문에

그냥 페이지에 따라 조건문 처리를 해 구현했다.

 

하지만, 이제는 4가지나 되고,

조건문 처리를 해서 만들기에는 코드가 너무 더러워진다.

그렇다고 같은 정보를 받고 있는 컴포넌트를

굳이 4개를 나눠 만드는 건 시간과 인력 낭비이다.

그리고, 나중에 시안이 더 늘어나게 되면?

한, 두개쯤은 몰라도 40개, 100개씩 늘어나게 되면 감당할 수 없다.

 

그래서 compound component pattern을 활용해

레고 블록을 조립하듯 컴포넌트를 끼워 맞춰보기로 했다.

 

compound component pattern란

여러 컴포넌트를 모아 하나의 복합적인 컴포넌트를 만드는 디자인 패턴이다.

 

 

이 컴포넌트로 보면,

좋아요를 누를 수 있는 하트 버튼,

이미지,

제목,

작성자,

작성 날짜

이렇게 나눌 수 있는 것이다.

 

이렇게 조합 가능한 컴포넌트를 다 나누고,

이를 블록 맞추듯 하나하나 조립하면 된다.

 

먼저, 이 꿀조합 컴포넌트는 다양한 페이지에서 사용된다.

따라서, Context API를 사용해 컴포넌트가 상태를 공유할 수 있도록 한다.

 

import { createContext } from 'react';

import type { Recipe } from '@/types/recipe';

interface RecipeItemValue {
  recipe: Recipe;
  children?: React.ReactNode;
}

export const RecipeItemValueContext = createContext<RecipeItemValue | null>(null);

const RecipeItemProvider = ({ children, recipe }: RecipeItemValue) => {
  const recipeItemValue = {
    recipe,
    children,
  };

  return <RecipeItemValueContext.Provider value={recipeItemValue}>{children}</RecipeItemValueContext.Provider>;
};

export default RecipeItemProvider;

 

 

그리고 해당 Provider를 꿀조합 컴포넌트에 감싸주어

상태를 공유할 수 있게 해 준다.

이제 children 부분에 원하는 컴포넌트를 넣어

새로운 컴포넌트를 만들어낼 수 있다.

 

const RecipeItem = ({ recipe, children }: RecipeItemProps) => {
  const { id } = recipe;

  return (
    <RecipeItemProvider recipe={recipe}>
      <Link to={`${PATH.RECIPE}/${id}`}>{children}</Link>
    </RecipeItemProvider>
  );
};

 

 

이제 초기 준비는 끝났다.

내가 조합하고 싶은 컴포넌트를 분리해서 구현해 보자.

 

const Title = () => {
  const {
    recipe: { title },
  } = useRecipeItemValueContext();

  return (
    <Text className={ellipsis} size="caption1" weight="semiBold" color="default">
      {title}
    </Text>
  );
};

 

 

context에서 받아온 정보를 바탕으로 각 컴포넌트를 구상한다.

다 구현했으면 이름에 맞춰 묶어준다.

이렇게 정의하면, 이 타이틀이 RecipeItem의 하위에 있는 Title임을 명확히 알 수 있다.

 

RecipeItem.Title = Title;

 

 

이제 <RecipeItem.Title> 형태로 어디서든 사용할 수 있다.

 

export const DefaultRecipeItem = ({ recipe }: RecipeItemProps) => {
  return (
    <RecipeItem recipe={recipe}>
      <RecipeItem.ImageAndFavoriteButton />
      <div style={{ height: '8px' }} />
      <RecipeItem.Title />
      <RecipeItem.AuthorAndCreatedDate />
    </RecipeItem>
  );
};

 

첫 번째, 꿀조합 컴포넌트를 이렇게 조립했다.

한 컴포넌트의 코드 길이가 짧고,

이름이 명시적으로 나와있기 때문에

확실히 알아보기가 편하다.

 

'아, 이 꿀조합 컴포넌트는 이미지와 좋아요 버튼이 있고,

타이틀이 있으며 작성자와 작성 일자를 가지고 있구나'를

코드만 보고서도 알 수 있게 되는 것이다.

 

이런 식으로 디자이너분들이 작업해 주신 Tob bar도

버전이 8개 정도 되었는데 한 개의 컴포넌트로 줄일 수 있었다.

 

 

 

compound component pattern이 완벽한 해결책일 수는 없다.

코드를 줄이기 위해 사용했지만,

compound component pattern을 사용한 컴포넌트가

오히려 각각 따로 컴포넌트를 정의한 것보다 길어질 수 있다.

 

이 꿀조합 컴포넌트만 하더라도

길이가 241줄에 도달한다.

(원래는 75줄이었다)

 

따라서, 이게 가장 좋은 방법인지

항상 충분히 고민하고 생각해 보자.

 

 

해당 PR 바로가기

댓글