본문 바로가기
STUDY/Project

React 컴포넌트화(Componentization)

by Y.Choi 2025. 5. 19.
728x90
반응형

 

React 같은 컴포넌트 기반 프레임워크에서 반복되는 UI 조각을 작은 단위의 재사용 가능한 컴포넌트를 나누는 과정을 컴포넌트 추출(Component Extraction) 또는 컴포넌트화(Componentization) 라고 한다.

 

다양한 곳에서 같은 형태의 UI를 공통 컴포넌트로 만들어 사용하도록 기본적으로 만들면 좋은 것들 부터 시작하겠다.

 

 

| Button 컴포넌트

버튼은 일관되면서도 위치나 기능에 맞게 색상을 다르게 하고 크기 조절도 가능하게 한다. 예를들면 글쓰기, 저장, 삭제, 목록 등 여러 상황에서 버튼을 자주 사용되는데 색상만으로도 직관적으로 알 수 있게 한다.

 

frontend/src/components/ui/Button.js

import React from 'react';
import classNames from 'classnames';

export default function Button({
  children,
  variant = 'primary', 
  size = 'md',          
  type = 'button',      
  className = '',
  ...rest
}) {
  const baseStyle =
    'rounded px-4 py-2 font-semibold transition-colors duration-200';

  const variantStyles = {
    primary: 'bg-blue-600 text-white hover:bg-blue-700',
    secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
    danger: 'bg-red-600 text-white hover:bg-red-700',
  };

  const sizeStyles = {
    sm: 'text-sm',
    md: 'text-base',
    lg: 'text-lg py-3 px-6',
  };

  return (
    <button
      type={type}
      className={classNames(baseStyle, variantStyles[variant], sizeStyles[size], className)}
      {...rest}
    >
      {children}
    </button>
  );
}

 

variant : 버튼 색상 테마

size : 버튼 크기

...rest : onClick, disabled 같은 추가 props 전달 

children : 버튼 안의 내용

classNames : Tailwind 클래스 병합을 깔끔하게 하기 위한 유틸 (설치 필요) 

- npm install classNames

- 조건에 따라 클래스가 달라져야 할 때 사용하면 좋다.

 

 

<적용>

import Button from '../components/ui/Button';

<Button type="submit" variant="primary">등록</Button>

 

 

이렇게 작은 프로젝트에서는 위와 같은 방식으로 하는 것이 적절 할 수 있다. 만약 더 큰 프로젝트라면 스타일만 분리해서 관리할 수 도 있다. 버튼 컴포넌트를 여러 테마로 커스터마이징 하거나 버튼 외에도 공통 컴포넌트 간 스타일을 공유해야 할 때 사용한다.

 

예) buttonStyles.js

export const buttonVariants = {
  primary: 'bg-blue-600 text-white hover:bg-blue-700',
  secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
  danger: 'bg-red-600 text-white hover:bg-red-700',
};

export const buttonSizes = {
  sm: 'text-sm',
  md: 'text-base',
  lg: 'text-lg py-3 px-6',
};

 

<적용>

import { buttonVariants, buttonSizes } from './buttonStyles';

 

 

이제, FormGroup, Label, Input, Textarea 등 재사용이 필요한 부분들에 대한 작을 이어가겠다.

 

 

| Label 컴포넌트

 

frontend/src/components/ui/Label.js

import React from 'react';
import classNames from 'classnames';

export default function Label({ htmlFor, children, className }) {
  return (
    <label
      htmlFor={htmlFor}
      className={classNames('block text-sm font-medium text-gray-700', className)}
    >
      {children}
    </label>
  );
}

 

기본 스타일을 유지하면서 className을 추가로 받을 수 있어 확장이 가능하다.

 

 

| Input 컴포넌트

 

frontend/src/components/ui/Input.js

import React from 'react';
import classNames from 'classnames';

export default function Input({ type = 'text', name, value, onChange, placeholder, className }) {
  return (
    <input
      type={type}
      name={name}
      value={value}
      onChange={onChange}
      placeholder={placeholder}
      className={classNames(
        'mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 text-sm',
        className
      )}
    />
  );
}

 

type, placeholder, value, onChange 등은 필요 시 props로 전달한다.

 

 

 

| Textarea 컴포넌트

 

frontend/src/components/ui/Textarea.js

import React from 'react';
import classNames from 'classnames';

export default function Textarea({ name, value, onChange, placeholder, rows = 5, className }) {
  return (
    <textarea
      name={name}
      value={value}
      onChange={onChange}
      placeholder={placeholder}
      rows={rows}
      className={classNames(
        'mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 text-sm',
        className
      )}
    />
  );
}

 

 

| FormGroup 컴포넌트

 

frontend/src/components/ui/FormGroup.js

import React from 'react';

export default function FormGroup({ label, htmlFor, children }) {
  return (
    <div className="mb-4">
      <label htmlFor={htmlFor} className="block text-sm font-medium text-gray-700 mb-1">
        {label}
      </label>
      {children}
    </div>
  );
}

 

Label을 안쪽에 직접 두지 않고 props로 받아 조립하는 방식이다.

 

 

<적용>

 

예) PostWrite.js

import FormGroup from '@/components/ui/FormGroup';
import Input from '@/components/ui/Input';
import Textarea from '@/components/ui/Textarea';

<FormGroup label="제목" htmlFor="title">
  <Input
    name="title"
    value={form.title}
    onChange={handleChange}
    placeholder="제목을 입력하세요"
  />
</FormGroup>

<FormGroup label="내용" htmlFor="content">
  <Textarea
    name="content"
    value={form.content}
    onChange={handleChange}
    placeholder="내용을 입력하세요"
  />
</FormGroup>

 

FormGroup : 라벨 + 입력 필드 세트 구조 정리용이다.

Label, Input, Textarea : 각각 독립적이고 조립 가능한 재사용 단위이다.

classNames를 사용해서 유연하게 커스터마이징 가능하다.

 

/ @의 의미

@는 src를 의미하고 ./src/components/ui/Button와 같은 뜻으로 상대 경로 지정시 불편함을 해결해준다.

이를 사용하기 위해서 별도 작업이 필요하다.

 

아마도.. 다음글 참고하면 될듯.

 

728x90
반응형