Component Architecture에 관한 고찰

Component Architecture에 관한 고찰

간단한 인사말

다들 잘 지내고 계신가요 ??,, 저는 그럭저럭 적적하게 살아가고 있습니다.
푹푹 찌는 더위와 불쾌하고도 짜증 나는 이 습한 날씨를 살아가는 여러분을 보면 정말 대단하다고 생각이 드는 요즘입니다.
저는 외출을 할 때 햇빛이 너무 쨍쨍해 견디지 못할 것 같을 때 은행, 도서관, 편의점 등 빵빵한 에어컨이 있는 곳을 자주 찾아 들어가곤 합니다. ㅎㅎㅎ 그럴 때마다 드는 생각이지만 에어컨을 발명하신 분은 노벨상을 받아야 마땅하다고 생각합니다.

그래서 찾아봤지요,, 에어컨을 발명하신 분은 윌리스 캐리어라는 아저씨인데 안타깝게도 노벨상을 타지 못했다고 하더군요 ㅜㅜ.
그 이유에 대해 찾아보았는데 상당히 웃겼습니다.(주관적인 관점)

에어컨 발명가가 노벨상을 받지 못한
이유

하하 저 사진이 사실은 아니고 노벨상 같은 경우는 대게 자연과학, 문학, 평화 등 특정 분야에 한정되어 있다고 합니다. 저라면 노벨상을 받지 못해 불만을 가질 것 같은데, 윌리스 캐리어 아저씨는 그렇지 않았나 봅니다. 왜냐하면 노벨상 말고 다른 상과 명예들을 많이 얻었다고 하는데요. 그중에서 자신이 에어컨을 발명한 게 대단하다고 생각했는지 자신의 이름을 딴 상들도 받았다고 합니다.(ex) Carrier Hall of Frame)

… … … … … … 끄응,,

어이쿠야,,, 주제와 너무 먼 얘기를 한 것 같습니다 🫢🫢.. 의식의 흐름 기법으로 글을 작성하는 건 무서운 것 같습니다. 타이틀과 모순되게 인사말이 간단하진 않지만, 뭐 어떻습니까 DOVI-LOG는 제 생각을 정리하는 곳이니 괜찮습니다 ㅎㅎ.

암튼, 이번 포스팅은 제가 개발하면서 가장 많이 사용하는 Component에 다뤄보고자 합니다. 제가 React.js를 사용하며 가장 많이 만들고 사용해 온 친구(Component)에 대한 고찰을 정리해보았습니다. 기술적인 얘기라 두근두근하네요 ㅎㅎ 빨랑 시작해보도록 하겠습니다. (코드박스가 포함되어서 모바일 환경에서는 가독성이 떨어질 수 있다는 점 양해 부탁드려요 ㅜㅜ 😓😓.)

Component Architecture (컴포넌트 아키텍처)

React_Component_Diagram

React.js는 컴포넌트 기반 아키텍처를 채택하고 있습니다.”

위 문장 같은 경우는 React.js의 특징을 검색하거나 공식문서에 들어가면 한 번쯤은 마주쳐 봤을 것으로 생각이 듭니다. 해당 개념에 대해 처음 접하는 분들이라면 “컴포넌트? 아키텍처?,, 그게 뭐지?? 🤔” 라며 다소 생소하실 수 있지만 반대로 프론트 개발을 좀 해보신 분이라면 재밌고 가볍게 봐주시면 될 것 같습니다 ㅎㅎ.

자 그럼, 차근차근 알아보도록 하죠!

컴포넌트란?

웹, 앱을 이루는 최소한의 독립적인 단위라고 합니다. 예시를 들자면 웹 페이지에서 헤더, 푸터, 인풋, 버튼 등등이 컴포넌트 (에어컨도 컴포넌트가 될 수 있습니다.) 라고 할 수 있습니다. 이러한 여러 유기체 같은 컴포넌트들은 서로 상호 작용하며 웹 페이지를 구성하게 됩니다. (어디선가 OOP의 냄새가…) 컴포넌트는 위에서 언급했듯, 독립적으로 이루어져 있습니다. 그래서 재사용, 테스트, 유지보수에 매우 용이하다는 특징을 가지고 있습니다. 주로 React.js나 Vue.js, Angular 등 다양한 웹 라이브러리 및 프레임워크들이 컴포넌트를 채택해 사용되고 있습니다. 더 나아가, 모바일에서도 사용되는 개념이랍니다.

아키텍처란?

Architecture(아키텍처)를 직역하자면 “건축학” 이라는 뜻을 내포하고 있습니다. 하지만 소프트웨어에선 이를 다르게 풀어서 설명하는데 “시스템 간의 흐름 및 구성, 동작 원리를 나타내는 것” 입니다. 아키텍처는 서비스의 전체적인 구조를 잡아주는 설계도 같은 역할을 함으로써, 개발자가 세부적으로 일일이 보지 않아도 해당 서비스의 구조 흐름(동작 원리)을 쉽게 파악할 수 있다는 특징을 가지고 있습니다. 코드를 어떻게 분리하고 모듈화할지, 객체를 어떻게 설계할지 등에 대한 고민을 해결해주는 좋은 지침서라고 할 수 있습니다.
예시로 유명한 MVC, MVVM, Clean Architecture 등이 있습니다. (추후에 다뤄보도록 하겠습니다.)

이 두 개념을 합쳐보자.

컴포넌트와 아키텍처를 알아봤으니 이젠 이 두 개념을 합쳐볼 시간입니다.
별거 없습니다, 컴포넌트 아키텍처입니다. 하나의 서비스를 구성 함에 있어 독립적인 유기체(컴포넌트)들을 상호 작용하도록 그려나가는 설계도를 만들면 됩니다. 이 과정에서 기업이나 개발자마다 컴포넌트를 설계하는 기준이 다 달라서 다양하고 재밌는 시스템(+ 더 나아가 폴더) 구조 형태를 많이 볼 수 있습니다.

하지만, 컴포넌트 아키텍처를 설계하기 위해선 유의할 점이 있습니다.
특히 복잡한 컴포넌트를 설계할 땐 말이죠…

재사용성

제가 가장 중요하게 생각하는 유의점입니다. 컴포넌트를 사용하는 이유는 재사용성의 목적이 가장 큽니다. 컴포넌트를 생성 및 분리할 때는 재사용성을 의의를 두어야 좋은 컴포넌트 아키텍처라고 할 수 있습니다.(주관적인 관점) 왜냐하면 재사용이 뛰어난 컴포넌트는 애초에 새롭게 만들 필요 없이 해당 컴포넌트만 불러와서 사용하면 되기에 개발을 훨씬 빠르고 탄탄하게 구성할 수 있습니다. 그래서 리소스 절감 면에서도 큰 이점을 챙길 수 있습니다. 아! 그리고 유지보수 면에서도 해당 컴포넌트만 관리하면 되기에 상당히 매력적이라고 할 수 있습니다. 그 예시와 중요성을 아래 코드와 함께 설명하겠습니다.

import React from "react";
import styled from "styled-components";

interface Props {
  text: string;
  type?: "button" | "submit" | "reset";
}

const A_Button = ({ text, type = "button" }: Props) => {
  const handleClick = () => {
    return window.alert("A 버튼 클릭");
  };

  return (
    <StyledA_Button type={type} onClick={handleClick}>
      {text}
    </StyledA_Button>
  );
};

export default A_Button;

const StyledA_Button = styled.button`
  padding: 5px 7px;
  border-radius: 15px;
  background-color: #000;
`;

복잡하진 않은, 이해를 위해 간단한 컴포넌트를 준비해봤습니다. 위 컴포넌트는 재사용이 힘든 컴포넌트라고 할 수 있습니다.
음,, 왜 그럴까요? 한 번 알아봅시다. 상황극을 한 번 해보겠습니다.
현재 저는 디자이너가 예쁘게 디자인한 페이지를 구현해야 하는 상황인데 여러 페이지에서 버튼 컴포넌트가 포함되어 있다고 가정하겠습니다.

이럴 때 어떻게 대처하면 될까요? 저는 2가지 방법이 떠오릅니다.

첫번째는 A_Button처럼 특정 페이지마다 해당하는 버튼들을 추가적으로 만들어 보는 것입니다. 하지만 큰 문제점이 있습니다. 페이지가 언제 변경되고 추가될지 모르는 상황에서 여러 버튼 컴포넌트를 생성하고 관리한다는 것은 개발자에겐 부담스러운 리소스로 다가올 수 있는데요. 심지어 위에서 언급한 재사용성을 만족하지 않는 방법인 것 같습니다. 어느 페이지에서든 재사용이 뛰어난 컴포넌트를 설계하려면 특정 도메인에 대한 의존성을 가져선 안 됩니다. 오로지 단일 책임을 지녀야 합니다. 즉, 버튼이라는 본질에 집중해야 한다는 것이죠.

그렇다면 정답은 두 번째가 되겠군요. 그 방법은 위에서 살짝 언급했듯, 단일 책임을 지닌 추상화된 컴포넌트를 만들어 보는 것입니다. 추상화(사과, 포도는 과일이라는 개념으로 묶는 느낌)된 컴포넌트는 특정 세부 컴포넌트를 구현할 때 그 기틀을 마련해주기에 재사용하는 데 있어서 떼려야 뗄 수 없는 친구 (🔥🥚) 라고 할 수 있습니다.

위 코드를 차근차근 고쳐보겠습니다.

우선 특정 도메인에 대한 의존성을 제거해보겠습니다. 위 코드는 A라는 도메인을 가지고 있습니다. 그래서 다른 페이지에서 사용되거나 확장하기 어려운 구조를 가질 수밖에 없게 되는데요. 이를 해결하기 위해서는 단일 책임을 잘 이루어야 합니다.

아래 코드를 보시면 A 도메인을 제거한 추상화된 버튼 컴포넌트입니다. 클릭 이벤트 함수 같은 경우는 Button을 사용하는 컴포넌트에 위임하여 props로 받아서 처리하도록 합니다.

import React from "react";

interface Props {
  text: string;
  type?: "button" | "submit" | "reset";
  onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
}

const Button = ({ text, type = "button", onClick }: Props) => {
  return (
    <button type={type} onClick={onClick}>
      {text}
    </button>
  );
};

export default Button;

이렇게 적절하게 특정 도메인 의존성 제거와 단일 책임을 갖게하는 것은 재사용성을 높여주는 방법이란 것을 알 수 있습니다. 이제 버튼 컴포넌트는 오로지 렌더링과 기본적인 속성만 담당하게 됩니다!

음 근데 지금 보니 뭔가 빠진 것 같습니다만, 엇 ㅎㅎ 스타일링이 빠져있습니다. 그리고 버튼에는 오직 onClick 이벤트만 가지고 있는 것을 볼 수 있습니다. 버튼은 onClick 외에도 다양한 이벤트와 요소들을 가지고 있는데(onFocus, onInput, disabled 등등) 이를 Props에 일일이 추가하는 건 노가다의 영역입니다. 노가다는 제가 제일 하기 싫어하는 행동인데요, 다행히 이를 하지 않도록 해주는 좋은 친구가 있습니다! 한 번 사용해서 구현해 보도록 하겠습니다.

props interface에 조금만 손을 봐주시면 됩니다. ButtonHTMLAttributes<HTMLButtonElement>를 사용하여 props interface에 확장하면 쉽게 구현할 수 있습니다. 마지막으로 customStyle을 추가해 개발자가 자유롭게 스타일링을 다룰 수 있도록 처리해주었습니다.

완성된 코드

import React from "react";
import styled, { RuleSet } from "styled-component";

interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
  text: string;
  customStyle?: RuleSet;
  // 나머진 내가 넣고 싶은 props를 넣으면 됨.
}

const Button = ({ text, customStyle, ...props }: Props) => {
  return (
    // ...props에는 확장한 버튼 이벤트, 요소들이 포함되어 있음.
    <StyledButton $customStyle={customStyle!} {...props}>
      {text}
    </StyledButton>
  );
};

export default Button;

const StyledButton = styled.button<{ $customStyle: RuleSet }>`
  ${({ $customStyle }) => $customStyle};
`;

재사용성을 위해 도메인 의존성 제거와 단일책임을 신경 써서 설계한 컴포넌트는 레고 단위처럼 서비스를 만들 수 있어서 정말 흥미로운 것 같습니다. 여기서 확실히 짚고 넘어가자면 제가 생각하는 기준에 의해서 설계된 재사용성이 높은 컴포넌트일 뿐 이 코드가 무조건 정답은 아니라는 점 알려 드립니다!!

현업 코드는 이보다 훨씬 복잡하고 파악하기 어려운 구조로 이루어져 있다고 알고 있습니다. 그래서 구조파악을 하는데 있어서 컴포넌트 아키텍처에 대한 깊은 이해가 중요한 것 같습니다.

마무리

이렇게 해서 컴포넌트 아키텍처가 무엇이고 재사용성의 중요성, 도메인 분리 및 단일책임에 대해 알아보았습니다. 앞으로 컴포넌트를 설계할 때는 이러한 개념들을 의식하여 개발에 집중해보도록 하겠습니다. 그럼 다음 포스팅에서 만나요 ㅎㅎ. 안녕!! 👋👋

레퍼런스