기술React중급-고급

React 디자인 패턴 완벽 가이드: Compound Components부터 Custom Hooks까지

럿지 AI 팀
|
2025-01-18
15

TL;DR - 이 글의 핵심

  • Compound Components: 유연한 UI 구조가 필요할 때
  • Render Props: 로직은 공유, UI는 위임할 때
  • Custom Hooks: 상태 로직을 재사용할 때
  • State Reducer: 제어 역전이 필요할 때

React Design Patterns

시니어 개발자로 가는 길

React로 개발하다 보면 “어떻게 하면 더 깔끔하게 짤 수 있을까?”라는 고민을 하게 됩니다. 이 글에서는 실무에서 자주 사용되는 React 디자인 패턴들을 코드와 함께 상세히 알아봅니다.

디자인 패턴을 배우면 코드의 재사용성과 유지보수성이 크게 향상됩니다. 특히 팀 프로젝트에서 일관된 패턴을 사용하면 코드 리뷰와 협업이 훨씬 수월해집니다. 더 자세한 내용은 Compound Components 심화 가이드에서 확인하세요.


1Compound Components 패턴

Compound Components는 여러 컴포넌트가 암묵적으로 상태를 공유하면서 하나의 완전한 UI를 구성하는 패턴입니다. HTML의 <select><option>의 관계를 생각하면 이해하기 쉽습니다.

문제 상황: Props 지옥

아래와 같은 Modal 컴포넌트가 있다고 가정해봅시다.

❌ Props가 너무 많은 경우
<Modal
  isOpen={isOpen}
  onClose={onClose}
  title="삭제 확인"
  description="정말로 삭제하시겠습니까?"
  showCloseButton={true}
  showFooter={true}
  primaryButtonText="삭제"
  secondaryButtonText="취소"
  onPrimaryClick={handleDelete}
  onSecondaryClick={onClose}
  size="medium"
  centered={true}
  // ... 더 많은 props
/>

Props가 10개가 넘어가면 유지보수가 어려워집니다. 새로운 요구사항이 생길 때마다 props가 늘어나죠.

해결책: Compound Components

✅ Compound Components 적용
<Modal isOpen={isOpen} onClose={onClose}>
  <Modal.Header>
    <Modal.Title>삭제 확인</Modal.Title>
    <Modal.CloseButton />
  </Modal.Header>
  <Modal.Body>
    <p>정말로 삭제하시겠습니까?</p>
    <p className="text-red-500">이 작업은 되돌릴 수 없습니다.</p>
  </Modal.Body>
  <Modal.Footer>
    <Button variant="ghost" onClick={onClose}>취소</Button>
    <Button variant="destructive" onClick={handleDelete}>삭제</Button>
  </Modal.Footer>
</Modal>

핵심 포인트

사용하는 쪽에서 구조를 자유롭게 결정할 수 있습니다. Footer가 필요 없으면 빼면 되고, Body에 뭘 넣을지도 마음대로입니다.

2Render Props 패턴

Render Props는 로직은 공유하되 UI는 사용처에서 결정하게 하는 패턴입니다. “어떻게 렌더링할지”를 함수로 전달받습니다.

마우스 위치 추적 예제

MouseTracker 컴포넌트
function MouseTracker({ render }) {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const handleMouseMove = (e) => {
      setPosition({ x: e.clientX, y: e.clientY });
    };
    window.addEventListener('mousemove', handleMouseMove);
    return () => window.removeEventListener('mousemove', handleMouseMove);
  }, []);

  // UI는 사용하는 쪽에서 결정
  return render(position);
}

// 사용 예시
<MouseTracker
  render={({ x, y }) => (
    <div>마우스 위치: ({x}, {y})</div>
  )}
/>

3Custom Hooks 패턴

React Hooks가 등장하면서 가장 널리 사용되는 패턴이 됐습니다. 상태 로직을 재사용 가능한 함수로 추출합니다. 관련 내용은 Custom Hooks 실전 가이드에서 더 자세히 다룹니다.

useAsync 커스텀 훅
function useAsync<T>(asyncFn: () => Promise<T>) {
  const [state, setState] = useState<{
    data: T | null;
    loading: boolean;
    error: Error | null;
  }>({ data: null, loading: true, error: null });

  useEffect(() => {
    setState(prev => ({ ...prev, loading: true }));
    asyncFn()
      .then(data => setState({ data, loading: false, error: null }))
      .catch(error => setState({ data: null, loading: false, error }));
  }, []);

  return state;
}

// 사용 예시
function UserProfile({ userId }) {
  const { data: user, loading, error } = useAsync(
    () => fetchUser(userId)
  );

  if (loading) return <Spinner />;
  if (error) return <Error message={error.message} />;
  return <Profile user={user} />;
}

4State Reducer 패턴

State Reducer는 사용자가 상태 변경 로직을 커스터마이징할 수 있게 합니다. Kent C. Dodds가 만든 패턴으로, 라이브러리 개발에 특히 유용합니다.

언제 사용하나요?

컴포넌트 라이브러리를 만들 때, 기본 동작은 제공하되 사용자가 필요에 따라 동작을 오버라이드할 수 있게 하고 싶을 때 사용합니다.


마치며

디자인 패턴은 은탄환이 아닙니다. 상황에 따라 적절한 패턴을 선택하는 것이 중요합니다.

Compound Components

유연한 UI 구조가 필요할 때

Render Props

로직 공유, UI는 위임할 때

Custom Hooks

상태 로직을 재사용할 때

State Reducer

제어 역전이 필요할 때

#React#Design Patterns#TypeScript#Frontend#Hooks