잘 만든 컴포넌트란? (with. Headless 컴포넌트)

rloo8·2024년 5월 22일
post-thumbnail

React에서 컴포넌트란?

리액트는 화면의 UI 요소를 컴포넌트 단위로 구분한다.
즉 컴포넌트란 리액트 애플리케이션의 구성 단위로, 각각이 특정 기능이나 UI 요소를 담당한다.

예를 들어, 레고로 집을 만든다고 생각해보자.
레고 블록으로 벽, 창문, 문, 지붕 등 특정 블록들을 만든다. 이렇게 만들어진 각각의 블록들을 조합하여 전체적인 집을 만든다.

이때, 각 레고 블록이 바로 리액트의 컴포넌트에 해당한다.
이러한 컴포넌트들을 조합하여 전체적인 UI를 만들어나가는 것이 리액트 애플리케이션 개발의 핵심이다.

잘 만든 컴포넌트란 무엇인가?

그렇다면 컴포넌트는 어떻게 나누고 설계하는 것이 좋을까?

📌 하나의 기능을 가지게 한다.
하나의 컴포넌트가 여러 가지 역할을 수행하게 되면 재사용성과 유지보수성이 떨어지게 된다.

📌 상태나 스타일은 최소화한다.
컴포넌트는 가능한 일반적으로 설계하고, 특정 동작이나 스타일은 외부에서 넘겨 받을 수 있도록 설계하는 것이 좋다.

즉, 컴포넌트는 재사용 가능하고 유지 보수하기 쉽게 만드는 것이 중요하다.


실습

1. 컴포넌트 만들기

Checkbox 컴포넌트와 Dropdown 컴포넌트를 만들어보자.

Checkbox.tsx

Checkbox 컴포넌트는 클릭하면 input 체크박스가 체크되고, 한번 더 클릭하면 체크가 해제된다.

import { useState } from 'react';

export function Checkbox() {
  const [isChecked, setIsChecked] = useState(false);
  
  const handleCheck = () => {
    setIsChecked(!isChecked);
  };
  
  return (
    <label>
      <h2>checkbox</h2>
      <input type="checkbox" checked={isChecked} onChange={handleCheck} />
    </label>
  );
}

Dropdown 컴포넌트는 h2 타이틀을 클릭하면 서브 타이틀이 보이고, 한번 더 클릭하면 서브 타이틀이 사라진다.

import { useState } from 'react';

export function Dropdown() {
  const [isOpened, setIsOpened] = useState(false);
  
  const handleOpen = () => {
    setIsOpen(!isOpen);
  };
  
  return (
    <div>
      <h2 onClick={handleOpen}>Dropdown</h2>
      {isOpened && <div>bla bla</div>}
    </div>
  );
}

두 컴포넌트는 UI는 다르지만, boolean 상태 값을 토글하여 UI를 변경한다는 점에서 같은 기능을 가지고 있다.
다시말해 로직이 중복된다.

2. 컴포넌트 분리하기

이렇게 중복되는 로직을 분리하여 별개의 컴포넌트로 만들 수 있다.

useToggle.tsx

import { useState } from 'react';

export function useToggle(init: boolean): [boolean, () => void] {
  const [state, setState] = useState(init);

  const handleState = () => {
    setState(!state);
  };

  return [state, handleState];
}

이 컴포넌트는 상태(state)와, 상태를 토글하는 이벤트핸들러 함수(handleState)를 반환한다.

3. 컴포넌트 재사용하기

분리해준 useToggle 컴포넌트는 hook처럼 다른 컴포넌트에서 불러 사용할 수 있다. (Custom Hook)

Checkbox와 Dropdown 컴포넌트를 아래와 같이 수정한다.

// Checkbox.tsx

import { useToggle } from './useToggle';

export function Checkbox() {
  const [isChecked, handleCheck] = useToggle(false);

  return (
    <label>
      <h2>checkbox</h2>
      <input type="checkbox" checked={isChecked} onClick={handleCheck} />
    </label>
  );
}
// Dropdown.tsx

import { useToggle } from './useToggle';

export function Dropdown() {
  const [isOpened, handleOpen] = useToggle(false);

  return (
    <div>
      <h2 onClick={handleOpen}>Dropdown</h2>
      {isOpened && <div>bla bla</div>}
    </div>
  );
}

이외에도 다양한 UI 요소에서 동일한 토글 기능을 쉽게 재사용할 수 있다.


Headless 컴포넌트

useToggle 컴포넌트처럼 스타일은 없고 로직만 존재하는 컴포넌트를 Headless 컴포넌트라고 한다.

장점

이렇게 UI와 로직을 분리하면 마크업과 스타일 수정이 자유롭기 때문에 재사용성이 높아진다.
개발자는 필요에 따라 해당 컴포넌트를 다양한 UI 요소에 적용할 수 있으며, 이를 통해 코드의 중복을 줄이고 유지보수성을 높일 수 있다.

단점

물론 모든 컴포넌트를 이렇게 분리하는 것이 좋은 것은 아니다.

로직을 분리하다보면 오히려 코드가 복잡해질 수 있다.
또한 UI가 없는 컴포넌트는 동작 방식을 이해하기 어려울 수 있기에 적절한 문서화와 코드 주석이 필요하다.

따라서 프로젝트의 특성과 요구사항을 고려한 적절한 판단과 활용이 중요하다.

0개의 댓글