Lighthouse of FE biginner

[React] 컴포넌트 설계하기 본문

[WEB] 프론트엔드

[React] 컴포넌트 설계하기

[FE] Lighthouse 2024. 6. 30. 14:40

컴포넌트는 리액트를 사용해 프론트엔드 개발을 한다면 반드시 마주치게 됩니다.
 

컴포넌트란 무엇일까요?

컴포넌트는 리액트에 국한된 개념이 아닙니다.
컴퓨터 공학을 공부하다보면 종종 컴포넌트라는 단어를 마주치게 되는데요. 여기서 컴포넌트란 독립된 모듈을 뜻합니다. 독립된 모듈들인 컴포넌트들이 그룹을 이루고, 그룹들이 모여서 소프트웨어가 되는 것 입니다.
리액트에서의 컴포넌트도 마찬가지의 개념입니다. UI를 구성하기 위한 독립된 모듈을 뜻하는데요, 컴포넌트들이 모여서 하나의 페이지를 이루고, 컴포넌트들이 모여서 웹 프론트엔드 애플리케이션이 됩니다.
 

리액트에서의 컴포넌트

리액트에서의 컴포넌트는 크게 두 가지로 구분할 수 있습니다. 특정 기능을 담고 있는 featured component와 어디서든 담백하게 사용할 수 있는 shared component입니다. 전자 컴포넌트는 특정 페이지, 특정 컴포넌트에서만 사용하는 기능을 담고 있어서 재활용이 크게 어려운 컴포넌트를 말하고, 후자 컴포넌트는 독립된 UI의 기능만을 수행해 어느 컴포넌트에서도 사용할 수 있는 컴포넌트를 말합니다. 예를 들면 버튼, 라디오, 체크박스, 테이블 컴포넌트를 말할 수 있습니다.
 

프로젝트에서 컴포넌트

프로젝트를 진행하다보면 종종 비슷한 UI, 비슷한 기능을 가지고 있는 개체들을 확인할 수 있고, 이러한 개체들을 컴포넌트로 분리하고는 합니다. 혹은 프로젝트 초기 단계에 공통 컴포넌트를 미리 개발하고 프로젝트를 진행하는 경우도 있습니다. 이렇게 공통으로 사용되는 컴포넌트 (shared component)는 초기에는 추상화가 잘 되어있어서 어느 곳에서든 쉽게 사용할 수 있고 쉽게 다른 컴포넌트로 재설계 할 수 있습니다. 하지만 프로젝트가 어느정도 진행되면서 컴포넌트에 이런 기능 저런 기능이 추가되고, 이 페이지에서는 조금 다른 스타일, 저 페이지에서는 조금은 더 다른 스타일이 추가되다보면 초기에 잘 추상화 된 컴포넌트에 이 코드 저 코드가 덕지 덕지 붙게 됩니다.
 
예를 들어서 조금 더 살펴보겠습니다. 프로젝트 초기에 아래와 같은 버튼 컴포넌트를 설계 했다고 가정합시다.

const Button = ({
  ...props
}: ButtonProps) => {
  return (
    <button
      className={cx([
        styles["storybook-button"],
      ])}
      style={style}
      {...props}
    >
      {label}
    </button>
  );
};

 
프로젝트 진행 중 프로덕트 디자이너가 다음과 같은 요청을 합니다. "개발자님, 이 페이지에서는 버튼에 이런 스타일이 있으면 좋겠어요"

const Button = ({
  ...props
}: ButtonProps) => {
  return (
    <button
      className={cx([
        styles["storybook-button"],
        "버튼에 이런 스타일"
      ])}
      style={style}
      {...props}
    >
      {label}
    </button>
  );
};

 
또 프로젝트가 진행이 되다 보니까 특정 기능이 붙게 되었습니다. 개발자는 옳지 않은 방법이라고 생각이 들었지만 일단 시간이 부족하니 기술 부채로 남겨놓고 추후에 리팩토링을 하기로 결정했습니다.

const Button = ({
  is특정상황,
  ...props
}: ButtonProps) => {
  return (
    is특정상황 && <button
      className={cx([
        styles["storybook-button"],
		"버튼에 이런 스타일"
      ])}
      style={style}
      {...props}
    >
      {label}
    </button>
  );
};

 
어느 정도 시간이 흘러 기술 부채를 해결하고자 하는 개발자는 아래와 같은 문제를 마주칩니다.

  1. 위 버튼 컴포넌트를 사용하는 페이지, 컴포넌트가 너무나 많다.
  2. is특정상황 이라는 기능을 리팩토링 하기 위해서 다시 기능을 재설계 해야한다.
  3. 공용 컴포넌트에서 "버튼에 이런 스타일" 이라는 특정 스타일을 제거하고 사용 하는 곳에서 스타일을 재정의 하기 위해서 해당 버튼 컴포넌트를 사용하는 모든 컴포넌트에서 UI가 깨지지 않는지 체크해야한다.

이것 보다 더 많거나 다른 문제를 마주칠 수 있겠습니다만, 단순히 도출할 수 있는 문제는 이와 같습니다. 결국 개발자는 많은 시간을 들여 리팩토링을 했거나, 혹은 리팩토링을 포기하고 기술 부채로 안고가는 결정을 내렸습니다.
 

어떻게 했어야 했을까?

위 예시를 통해 문제되는 상황을 살펴봤습니다. 그럼 개발자는 어떻게 했어야 할까요?
 
먼저 소프트웨어 공학에서 가장 지향하는 모듈 설계는 다음과 같습니다. "응집도를 높이고 결합도를 낮춰라." 여기서 응집도는 모듈(컴포넌트)의 내부 요소들의 연관 관계를 의미합니다. 결합도란 다른 모듈과의 상호 의존 관계를 뜻합니다.
 
컴포넌트를 설계할 때도 마찬가지의 관점에서 생각해볼 수 있습니다. "공통으로 사용되는 컴포넌트는 특정 기능과 UI가 묻지 않고 독립된 역할을 할 수 있게 설계해야 한다." 입니다.
 
위 사례에서 생각을 해본다면, 어떤 컴포넌트에서 버튼에 이런 스타일을 추가적으로 입혀야한다는 요구사항에는 해당 컴포넌트에서 스타일을 정의해 props로 스타일을 내려주고, 컴포넌트에서는 props만 받아서 스타일을 입힐수 있습니다. 특정 기능에서 버튼을 없애는 상황이라면 props로 boolean값을 내려주기 보다는 해당 컴포넌트를 사용하는 측에서 분기를 한다면 공용 컴포넌트를 수정하지 않고 기능을 구현할 수 있게 됩니다. 이는 가장 기본적인 설계 방식이지만 프로젝트를 진행하다보면 시간이 촉박하거나 경험이 부족한 개발자라면 충분히 놓칠 수 있는 부분입니다.
 

마치며

재활용 할 수 있는 컴포넌트를 설계한다는 것은 프론트엔드 개발자가 반드시 갖춰야 할 덕목 중 하나 입니다. 하지만 가장 어려운 것 역시 재활용 가능한 컴포넌트를 설계한다는 것 입니다.
 
프로젝트가 한창일때 역시 재활용 가능한 컴포넌트 관점에서 사고를 하는 습관을 들여야겠습니다. 저는 항상 이렇게 생각하면서 작업을 하고 있습니다. " 가장 담백한 컴포넌트를 만들자."