본문 바로가기
Project/Pennyway

최고의 공통 컴포넌트를 찾아서

by 이의찬 2024. 5. 21.

0. TL;DR

  • 작업의 효율성과 유지보수성을 위해 고려해서 공통 컴포넌트를 구현
  • 문제 발생, 원인은 `명확성`을 충분히 고려하지 않았기 때문
  • 이를 개선하고, 공통 컴포넌트가 고려해야할 `명확성`에 대해서 이해

1. Why?

여러 차례 프로젝트를 진행하며, 어느정도 초기 설정이 끝나면 그 다음으로 공통 컴포넌트를 구현하게 된다. 이번 Pennyway에서도 마찬가지였는데, 이를 구현해서 

  1. 작업의 효율성 높이기
  2. 유지보수 용이하게 하기

에 도움을 줄 수 있을 것이다. 하지만 프로젝트가 그렇게 쉽지만은 않은 법이다. 이번 글에서는 Pennyway에서 구현한 공통 컴포넌트에서 어떤 문제를 겪었고, 어떻게 컴포넌트를 수정해서 문제를 해결했는지 작성해보고자 한다. 


2. 1차 코드 구현

1차로 구현한 공통 컴포넌트들

구현 코드

가장 많이 고민한 부분이자 이후 수정하게 되는 Button 컴포넌트만 간단하게 소개하고자 한다. 나름대로 공통 컴포넌트의 역할과 확장성을 고려해서 개발하고자 했다.

  • 복잡성 제어를 고려해 isDisabled 속성에 따라 별개의 컴포넌트로 구현했다.
    • ActiveButton : disabled props를 가지고, 이에 따라서 디자인이 변경되는 Button
    • Basic Button : 기본적인 Button
    • 하나의 컴포넌트로 만드는 것도 고려했지만, optional props가 사용자에게 혼동을 줄 수 있다고 생각했다.
  • style 요소의 경우에도 미리 Union Type으로 선언해두고 사용하는 것이 안전할 것이라고 생각했다. 
  • 공통으로 사용되는 ButtonProps 선언 후 이를 상속받아 사용했다.
//ActiveButton.tsx
export type ActiveStyle = 'confirm' | 'follow-sm' | 'follow-lg';

interface ActiveButtonProps extends ButtonProps {
  styleClass: ActiveStyle;
  isDisabled: boolean;
}

export const ActiveButton = ({ onClick, styleClass, isDisabled, children }: ActiveButtonProps) => {
  const sytleClassname = isDisabled ? styleClass : `${styleClass}-disabled`;
//...
//BasicButton.tsx
export type BasicStyle = 'defalut' | 'confirm-cancle' | 'bsm-cancle' | 'bsm-option';

interface BasicButtonProps extends ButtonProps {
  styleClass: BasicStyle;
}

export const BasicButton = ({ onClick, styleClass, children }: BasicButtonProps) => {
//...

3. 문제점

겪은 문제점

위의 Button 컴포넌트들로 본격적인 개발을 진행하자, 금세 문제점을 알 수 있었다.

  1. 추가적인 props를 주고 싶은 경우 공통 컴포넌트를 수정해야하기에 부담된다.
  2. 모든 Button 컴포넌트의 styleClass를 공통 컴포넌트의 type에 추가해야만 하는 것이 지나치게 불편하다.
  3. 이 과정에서 에러가 자주 발생한다.

왜 이런 문제가 발생했을까?

이게 아니야!

가장 큰 이유는 모든 Button을 공통 컴포넌트를 이용해서 처리하려고 했던 부분이였다. 그 결과 모든 Button들이 공통 Button 컴포넌트에 종속되게 되었고, 불편함을 느낄 수 밖에 없게 된 것이다.

 

이는 공통 컴포넌트의 `명확성`을 고려하지 않아 나타난 문제라고 생각한다. 기존 컴포넌트의 경우에서는 최대한 재사용성과 유지보수성을 높이고자 했고, 이를 위해 `확장성`을 고려했다. 하지만 `명확성`에 대해서는 충분히 고려하지 못했다. 

 


4. 명확성을 고려해 개선하기

공통 컴포넌트의 명확성

그렇다면 `명확성`은 왜 중요한 것일까? 공통 컴포넌트에서 `확장성`은 너무나도 당연한 부분이고, `명확성`을 고려한 이유는 만약 확장성만을 고려한다면, 모든 공통 컴포넌트는 빈 element 태그로 통일될 것이기 때문이다. 

export default function BestSharedComponent({ children }: { children: ReactNode }) {
  return { children };
}

최고의 공통 컴포넌트(상상)

위의 예시는 조금 극단적인 예시다. 그렇다면 props 확장을 통해서 컴포넌트를 처리해준다면 어떨까? 하지만 이 역시 한계는 있다.

 

A라는 역할을 사용하도록 만들어진 컴포넌트가 A'역할, A''역할을 담당하게 되는 것은 적절한 확장이다. 공통 컴포넌트는 재사용을 통해서 개발시간 단축...(중략)을 위해서 만드는 것이니까.

 

하지만 B역할이나 C역할까지 담당하게 된다면, 필연적으로 사용하는 쪽에서 입력해야하는 props가 늘어날 수 밖에 없고 이는 결과적으로 오히려 개발시간을 늘리고, 유지보수성을 떨어트리게 된다. 이것이 props를 통한 확장의 한계다.

 

아래의 코드는 내가 삼성청년SW아카데미 내 첫 프로젝트에서 겪은 실화다.

  • Input 컴포넌트 Props(갖다 쓰기 전)
// 이랬는데
interface InputProps {
  type: string;
  text: string;
  notice: string | null;
}
  • Input 컴포넌트 Props(갖다 쓴 후)
// 요래됐슴당~
interface InputProps { 
  type: string;
  text: string;
  notice?: string;
  state?: string;
  keyName?: string;
  getter?: object;
  setter: React.Dispatch<React.SetStateAction<object>>;
  value?: string;
  placehoder?: string;
  disabled?: boolean;
}

원래는 단순히 입력만을 고려해서 만들어진 Input 컴포넌트였지만, 수정과 disalbed 등 여러 속성등을 고려하게 되며 엄청나게 많은 props들을 가지게 되었다. 위의 컴포넌트는 분명 여러 부분에서 공통으로 사용하기 위해 많은 props를 추가했지만, 더 이상 공통 컴포넌트로 사용하기는 힘들 것이다.

 

내가 만든 공통 컴포넌트도 마찬가지다. 모든 곳에서 사용할 수 있는 컴포넌트로 만들고자 했고, 그 결과 사용자가 불편해지고 관리도 어려워져 결국 아무도 쓰지 않는 컴포넌트가 된 것이다.

개선하기

이를 해결하기 위해서, 다음과 같이 공통 컴포넌트를 수정했다.

  1. 여러 페이지에서 재사용되는 Button만 공통 컴포넌트로 구현
  2. 이를 제외한 나머지 컴포넌트들은 각자의 slice에서 정의하고 사용

공통 컴포넌트로 사용되는 버튼들


5. 래퍼런스