React로 범용적인 재사용 컴포넌트 만들어보기

2020. 11. 30

컴포넌트

컴포넌트란 독립적인 단위모듈인데, 이는 말 그대로 독립적으로 사용(수행)할 수 있다는 것을 말한다.

그런데 뭔가 독립적이란 것이 조금 추상적이라고 느껴졌다. 🤔

그만큼 컴포넌트를 나누는 일은 이 독립이라는 단어를 어떻게 해석하느냐에 따라서 방법이 바뀌게 된다.

리액트와 같은 SPA 라이브러리 혹은 프레임워크를 사용하다보면 컴포넌트들을 만들게 되는데, 이렇게 만든 컴포넌트들은 특정 화면에서 사용이 될 것이다.

그리고 이 컴포넌트들은 규모가 작은 애플리케이션에서는 적을 수 있지만 규모가 커질수록 컴포넌트의 갯수와 크기도 비례하여 커지게 된다.

이에 따라 프론트 개발자는 아마 이런 고민들을 하게 된다.

"음.. 어떻게 하면 컴포넌트를 재사용 할 수 있게 개발 할 수 있을까...?"
"유지보수가 쉽도록 컴포넌트를 만들 순 없을까...?"

또한 이런 고민은 프로젝트의 규모나 도메인 등에 따른 관심사의 분리(Separation of concerns)에 영향을 받아 어떻게 개발해야할지, 어떤 방식이 효율적인지에 따라서 방향성을 잡기도 한다.

별거 아닌 것 같지만 정말 심오한 문제들..🤔

가장 접근하기 편하고 직관적인 방법인 도메인 중심으로 만들어진 컴포넌트를 예로 들자면..

딱 봤을때 정말 직관적인게 느껴진다. 이런 도메인 단위 컴포넌트는 소규모에서는 정말 편하게 개발할 수 있지만, 조금만 규모가 커지더라도 엄청나게 많은 도메인과 디렉토리로 인해 쉽지 않은 개발이 되는 경우도 있다.

또한 재사용성에서 본다면 도메인 단위는 그 도메인 단 하나만을 위해 사용되기 때문에 굉장히 떨어지게 된다.

그래서 조금이라도 규모가 있는 프로젝트를 진행한다면 재사용성까지 고려해서 컴포넌트를 구현하는 것이 아무래도 개발 뿐만 아니라 유지보수에서도 훨씬 수월해진다.

최근에는 아토믹 디자인(Atomic Design) 이라는 레이아웃 단위의 컴포넌트로 쪼개서 레고처럼 조립해서 만드는 개발론을 사용하여 개발하는 케이스도 있다고 한다.

그래서 재사용 단위의 가장 간단하고 기초적인 부분이기도 하고, 유용하게 사용될 수 있는 컴포넌트를 분리하는 시간을 가져보려고 한다.

가장 범용적인 컴포넌트

우리가 프론트 개발을 할 때 가장 범용적으로 쓰이는게 무엇이 있을까?

도메인에 따라 여러가지가 있겠지만, 항상 사용하는 요소들은 span, p, button, div, img 등이 있다.

보면 span과 p는 공통적으로 텍스트를 감싸는 태그인데, 이 두개를 한 번에 관리하는 컴포넌트가 있으면 참 편할 것 같다는 생각이 번쩍 들었다.

Text

import React from 'react'

/**
 * 텍스트
 * @param {tag} 텍스트에 사용할 태그
 * @param {className} 클래스명
 * @param {children} 텍스트의 내용
 */
const Text = ({ tag: Tag = 'span', className, children }) => {
  return <Tag className={className}>{children}</Tag>
}

export default Text

보면 porps로 tag와 className, children을 받아오고 있다. 여기서 주의깊게 봐야하는 부분이 tag인데, tag는 이 컴포넌트를 사용할 때 직접 prop을 통해서 가져오고, 그걸 태그에 동적으로 넣어줌으로서 그려준다.

만약 tag를 넘겨주지 않으면 기본적으로 span을 사용하게 했다.

실제로 어떻게 사용되나 보면

import React from 'react'
import Text from './components/Text'

function App() {
  return <Text>Text 컴포넌트 구현</Text>
}

export default App

추가로 prop으로 tag를 p로 넘기게 되면..

import React from 'react'
import Text from './components/Text'

function App() {
  return <Text tag="p">Text 컴포넌트 구현</Text>
}

export default App

위와 같은 방식으로 태그를 명시하면 이제 p 태그로 사용할 수 있게 된다. 이처럼 앞으로 컴포넌트를 개발할 때는 텍스트 태그를 사용할 때 이 Text 컴포넌트 하나만을 가지고 사용하면 되는 것이다.

또 하나의 예를 들어보자

Button

버튼도 텍스트와 크게 다르지 않다. 버튼의 기능을 생각해본다면 내부에 텍스트를 가진 하나의 버튼이고, Click 이벤트를 가지고 있으며 특정 타입에 따라 다르게 동작한다.

이런 특성을 이용하여 한 번 구현해보자

import React from 'react'

/**
 * 버튼
 * @param {type} 타입
 * @param {className} 클래스명
 * @param {width} 넓이
 * @param {height} 높이
 * @param {margin} 마진
 * @param {padding} 패딩
 * @param {onClick} 클릭 이벤트
 * @param {children} 버튼 내용
 */
const Button = ({
  type = 'button',
  className,
  width,
  height,
  margin,
  padding,
  onClick,
  children,
}) => {
  return (
    <button
      type={type}
      className={className}
      onClick={onClick}
      style={{ width, height, padding, margin }}
    >
      {children}
    </button>
  )
}

export default Button

위 특성들을 고려하면 children에 대한 스타일이 필요할 것이고, 버튼 타입, 그리고 onClick 이벤트를 받으면 버튼과 동일할 것이다.

버튼도 사용해 보자

import React from 'react'
import Button from './components/Button'
import Text from './components/Text'

function App() {
  return (
    <Button padding="10px 20px 10px 20px" onClick={() => console.log('버튼 클릭 완료')}>
      <Text>버튼</Text>
    </Button>
  )
}

export default App

이렇게 내가 원하는대로 바로 사용할 수 있다.

현재까지 이정도 규모로 봤을 때는 오히려 불편하고 번거롭기만 할 것 같지만, 컴포넌트가 많아질수록 재사용 할 수 있는 부분이 늘어나 개발에 많은 도움이 될 것이라 생각된다! 👍