ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 변경에 유연한 컴포넌트 설계
    React 2024. 6. 17. 20:04

     

    우리는 귀에 딱지가 앉도록 듣는 말이 있습니다.

    ‘컴포넌트를 잘 설계해야 한다.’

     

    컴포넌트 설계 기준이 뭔지 같이 한 번 살펴봅시다.

     

    컴포넌트 설계…?

    설계의 뜻이 뭘까요?

     

    음..

    컴포넌트를 ‘만든다’로 쉽게 생각해도 될 것 같아요.

     

    컴포넌트 설계를 ‘잘’ 설계한다 = 컴포넌트를 ‘잘’ 만든다.

     

    ‘잘’ <— 이게 핵심이네요.

     

    그럼 컴포넌트를 잘 만든다는건 어떤 의미일까요?

     

    컴포넌트를 잘 나누는 것일까요?

    그렇다면 컴포넌트를 나누는 이유는 뭘까요?

    재사용 가능하게 나눠야한다는 뜻일까요?

     

     

     

    컴포넌트의 뜻부터 같이 알아보죠.

     

     

     

    좀 더 개발적인 의미로 들어가볼까요?

     

     

    리액트 공식문서를 살펴보니, UI를 구성하는 요소라고 하네요.

     

    우리가 흔히 말하는 컴포넌트를 재사용한다는 의미가,

     

    결국 UI를 재사용하기 위해 컴포넌트를 잘 만들어야한다고 말하는걸까요?

    혹은 기능을 재사용하기 위함일까요?

    아니면 둘 다?

     

    제 생각에는 UI + 기능을 재사용하는 것이 진정한 컴포넌트의 재사용이라고 생각합니다.

     

    그럼 재사용이 가능하다는 의미의 범위가 어디까지 일까요?

     

    단순히 다른 곳에서 한 번 더 쓰이면 재사용인지

    모든 곳에서 쓰일 수 있어야만 재사용인지..?

     

     

     

     

    제 경험 공유하며 같이 고민해보도록 하죠!

     

     

    어느 날, 같은 팀 디자이너 분이 저를 애타게 찾더군요.

     

     

    벌써부터 느낌이 안좋습니다.

    제품의 요구사항이 바뀐 것인데요,

     

    디자이너 팀원들의 요구사항은 아래와 같았어요.

     

     

    제품의 디자인이 바뀐 이유는

    1. 유저에게 온보딩 단계를 더 가시적으로 보여줄 필요가 있다.

    2. 유저가 입력한 '토너먼트 종료일'와 '선물 전달일'의 순서를 더 명확하게 하기 위한 디자인이 필요하다.

    3. 카카오톡 공유에 한정된 부분을 일반 공유하기로 대체한다.

    4. 어차피 일반 공유하기에 링크 복사 기능이 있으니, 링크 복사 버튼을 삭제하고, 입장하기 버튼을 추가한다.

     

    오! 제품이 좀 더 디벨롭되는군요! 개발자인 저도 기분이 좋습니다 ㅎㅎ

     

    그건 그렇고, 음… 디자인이 싹 다 바뀌었네요?

     

    스프린트가 얼마 남지 않았던 이슈로,

     

    조금 더 효율적으로 일하기 위해 재사용 할만한 요소들을 찾아보겠습니다.

     

     

     

    타이틀 UI요소(빨간박스), 선물 전달일 및 토너먼트 진행 시간(초록박스) 정도가 되겠네요.

     

    빨간박스는 UI만 담당하고, 텍스트 몇 자 바뀐 것이기 때문에 일부만 수정한다면 재사용이 가능하겠군요.

     

    초록박스는 기능은 그대로 남겨둔 채로, UI만 수정하면 될 것 같아요.

     

    초록박스 컴포넌트의 역할은 다음과 같습니다.

     

    1. UI적으로는 토너먼트 종료일 부분을 제외하고는 똑같습니다.

    2. 기능적으로 온보딩 단계에서의 날짜 계산을 파싱하는 역할을 담당합니다.

     

    그렇다면 저 파란박스 내 버튼들은 어떻게 해야할까요?

    스프린트 기한이 며칠 남지 않았는데, 새롭게 구현할 생각하니 막막합니다...

     

    다른 팀원들의 뷰에서 가져다 쓸만한 것은 없는지 살펴보겠습니다.

     

     

    오!! 여기 토너먼트 최종 결과물 뷰에서 파란박스 내 버튼들을 재사용하면 금방 구현할 수 있겠군요!

     

    버튼의 텍스트와 기능은 다르고, UI만 같네요.

     

    이제 코드를 살펴보러 가봅시다.

     

     

     

     

    음.. 지금 구조에선 '다시하기' 텍스트 하나만 바뀌어도 재사용이 불가능 할 것 같네요.

     

    그러나 시간이 부족했던 스프린트 특성과, 제 실력 이슈로!!

     

    일단 복붙하고, 네이밍과 기능만 바꿔서 진행해 봅니다. (아주 비효율적이죠?)

     

     

     

     

    이제부터 차근차근 공통으로 쓰일 수 있게 작업해봅시다.

     

    기능은 다르고 UI는 완전히 같아요. 

    (토너먼트 결과 버튼은 '결과 제출하기' '다시하기' / 온보딩 결과 버튼은 '공유하기' '입장하기'이기 때문)

     

    그러나

     

    현재 네이밍(빨간박스)으론 토너먼트 결과 footer와 온보딩 결과 footer에서만 버튼이 쓰일 수 있겠죠?

     

    더 넓은 사용성을 위해 일단

    도메인을 제거해야할 것 같아요.

     

    CommonButton이라는 이름을 주고, children을 활용해봅시다!

     

     

    음... 이렇게 하니까 문제가 있네요. 위 CommonButton을 가져다 쓰는 쪽에서 children이 같은 값이 들어가버려요.

    그래서 버튼의 text가 '공유하기' '공유하기' 처럼 똑같이 들어가는 문제가 있네요.

     

     

    자 여기서 드는 생각이 있죠.

     

    '버튼 두개를 그냥 하나하나 따로 분리해버려..?'

     

     

    일단 버튼 두개를 같이 묶은 상태로 진행해봅시다.

     

    새롭게 props를 설정해봅시다.

     

     

     

     

    onClick은 버튼 태그의 기본 props를 상속받았기에 지워주고,

     

    left와 right라는 네이밍을 통해 왼쪽 버튼의 텍스트와 클릭 시 핸들러, 오른쪽 버튼의 텍스트와 클릭 시 핸들러를 정의해주었어요!

    - 좀 더 직관적인 네이밍 이용을 의도했습니다.

     

     

    이제 공통으로 빼줬으니, 한 번 가서 사용을 해볼까요?

     

     

     

    음... 이렇게 바꿔봤는데 어떤가요?

     

    원래 코드보다는 재사용이 가능할 것 같아요.

    이제 온보딩 최종 단계footer와 토너먼트 결과 뷰 footer에서 같이 가져다 쓸 수 있게 되었어요!

    '비교적' 변경에 유연한 UI를 가지게 되었습니다.

     

    그러나,,,,

     

    더 걸리는 문제가 있군요.

     

    우선 저는 버튼을 두 개를 같이 묶었는데,

    네이밍부터 CommonButtons가 아니라 CommonButton인게 거슬려요.

    이를 일단 수정해야할 것 같아요.

     

    또한 버튼은 아무래도 구조상 레이아웃이 변경될 가능성이 굉장히 높은데,

    과연 지금과 같은 구조에서는 버튼의 레이아웃이 변경되었을 때 유연하게 대처가 가능한지 의문입니다.

     

    이제 이런 요구사항이 있다고 가정해보죠.

     

    '온보딩 최종단계 footer의 왼쪽 버튼의 색깔을 다르게 정의해주세요'

    '토너먼트 결과 뷰 footer의 오른쪽 버튼에 화살표를 추가해주세요.'

     

     

     

    기존의 방법대로라면,

     

     

    이렇게 props를 추가해서 구현을 할 수 있을겁니다.

     

    그러나, 컴포넌트의 가치가 떨어지는 것 같은 불안한 느낌이 들죠...

     

    기껏 재사용성을 고려하여 만들어놨는데,

     

    추가적인 요구사항에는 대응하지 못하는 컴포넌트가 되어버린 것만 같아요.

     

    애초에 설계를 잘하지 못한걸까요? 

     

    우리는 어디까지 고민하며 컴포넌트를 설계해야 할까요?

     

    이제부터 버튼에만 집중해서 변경에 유연한 버튼 컴포넌트를 만들어 봅시다.

     

    버튼이 두 개로 묶여있던 부분을, 

    하나의 버튼으로 다시 재설계해야할 것 같아요.

     

    interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
      variant: 'default' | 'secondary';
      onClick: () => void;
    }
    
    const Button = ({ children, variant = 'default', onClick, ...props }: ButtonProps) => {
      return (
        <button css={[btnStyle, btnVariant[variant]} onClick={onClick} {...props}>
          {children}
        </button>
      );
    };
    
    //스타일 코드 생략

     

    이렇게 버튼을 variant로 나누어 설계해봤어요.

     

     

    버튼의 확장 가능성을 고려하여 size props까지 추가해볼까요?

     

    interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
      variant: 'default' | 'secondary';
      size: 'small' | 'large'; //size props 추가
      onClick: () => void;
    }
    
    const Button = ({ children, variant = 'default', size = 'large', onClick, ...props }: ButtonProps) => {
      return (
        <button css={[btnStyle, btnVariant[variant], btnSize[size]]} onClick={onClick} {...props}>
          {children}
        </button>
      );
    };
    
    //스타일 코드 생략

     

     

     

     

    추후 버튼에 icon이 추가될 수 있는 가능성까지 염두에 두어 다시 설계해봅시다.

    interface ButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
      variant: 'default' | 'secondary';
      size: 'small' | 'large'; 
      onClick: () => void;
      icon?: ReactElement; //icon props 추가
    }
    
    const Button = ({ children, variant = 'default', size = 'large', onClick, icon, ...props }: ButtonProps) => {
      return (
        <button css={[btnStyle, btnVariant[variant], btnSize[size]]} onClick={onClick} {...props}>
          {children}
          {icon} //icon 추가
        </button>
      );
    };
    
    //스타일 코드 생략

     

     

     

    이제 사용하러 가볼까요?

     

    <Button //아이콘이 있는 버튼
                variant="default" //default 버튼
                size="small"
                onClick={공유하기 로직}
                icon={
                  <IcArrow />
                }
              >
               공유하기
              </Button>
              
    <Button variant="secondary" //secondary 버튼
            size="large" onClick={입장하기 로직}>
               입장하기
              </Button>

     

    어떤가요? 버튼 두개로 묶여있던 CommonButton 컴포넌트를 

     

    버튼 하나하나로 분리하였고,

     

    재사용성을 더욱 고려하기 위해 

     

    버튼에 variant와 size등을 추가하여 재설계 해보았습니다.

     

    기획자 분들과 디자이너 분들의 추가적인 요구사항 가능성이 있는 icon까지 염두에 두어 설계하였습니다.

     

    더욱 변경에 유연해지지 않았나요?

     

     

     

     

     

     

     

     

     

    **결론을 정리해보겠습니다.

     

    컴포넌트를 잘 설계한다 = 컴포넌트를 잘 만든다 = 컴포넌트를 잘 나눈다 = 재사용하기 용이하다?

     

    그럼 우리는 어디까지 재사용을 고려하며 개발해야 하는가?

    아직 일어나지 않은 추가적인 요구사항을 대응할 정도로 컴포넌트 설계를 고려해야 하는가?

     

    그래서 너만의 컴포넌트 설계 기준이 뭔데?

     

    1. 버튼은 레이아웃이 변경될 가능성이 높으니 무조건 재사용성을 고려하여 설계하고,

    기획자 & 디자이너 선생님들의 의견과 초점에 맞추어 설계한다.

     

    2. 피그마에 두 번 이상 보이면 일단 분리할 컴포넌트로 리스트업 한다.

     

    3. 컴포넌트로 분리했을 때 재사용이 가능할 컴포넌트인지 고려한다.

     

     

    이 아티클을 통해 저는, 제가 컴포넌트를 바라보는 관점을 여러분들께 알려드리기 위해 노력하였는데요.

     

    결국 우리는 끊임없는 고민과 시행착오를 겪으며, 나만의 기준을 세워 컴포넌트를 바라보아야 하는 것은 아닐까요?

     

    이 아티클을 접하시고, 컴포넌트 분리의 원칙을 그대로 받아들이는 것이 아니라,

     

    새로운 요구사항에 우리가 어떻게 대응하고 있는지 성찰하고,
    더 개선할 수 있는 부분이 있는지 살펴보고 고민해보며
    자기 코드의 애정을 가지고 

    스스로 컴포넌트를 설계하는데 있어서 더 깊고 다르게 고민하는 것.


    이런 부분에 대해서

    끊임없이 개선하는 노력을 기울이는 개발자가 되시기를 기원하며

     

    아티클 마치겠습니다.

     

    - 장정안

     

     

     

    참고 레퍼런스 꼭꼭 보시는 걸 추천드립니다! 

    정말 많은 인사이트가 담겨있어요!!!

     

    참고 레퍼런스:

    https://www.youtube.com/watch?v=fR8tsJ2r7Eg

    https://fe-developers.kakaoent.com/2022/220731-composition-component/

    https://www.youtube.com/watch?v=HYgKBvLr49c

    https://fe-developers.kakaoent.com/2023/230330-frontend-solid/

    https://fe-developers.kakaoent.com/2024/240116-common-component/

     

     

Designed by Tistory.