React 디자인 패턴 / HOC 패턴

발행: (2025년 12월 26일 오전 10:05 GMT+9)
8 min read
원문: Dev.to

Source: Dev.to

Higher‑Order Component (HOC) Overview

Higher‑Order Component (HOC)는 컴포넌트를 인수로 받아 일부 로직을 추가하고, 그 로직을 포함하는 새로운 컴포넌트를 반환하는 함수입니다.
HOC를 사용하면 코드를 반복하지 않고 여러 컴포넌트에 걸쳐 동작을 재사용할 수 있습니다.

1️⃣ HOC로 스타일 추가하기

컴포넌트마다 style 객체를 만들지 않고, 전달하는 모든 컴포넌트에 동일한 스타일을 주입하는 HOC를 만들 수 있습니다.

// withStyles.tsx
function withStyles(Component: React.ComponentType) {
  return (props: T) => {
    const style = { padding: '0.6rem', margin: '4rem' };
    return ;
  };
}

사용법

const Button = () => Click me!;
const Text   = () => 
Hello World!
;

const StyledButton = withStyles(Button);
const StyledText   = withStyles(Text);

StyledButtonStyledTextwithStyles가 제공하는 style prop으로 강화된 원래 컴포넌트입니다.

2️⃣ HOC로 로딩 인디케이터 추가하기

데이터를 가져올 때 요청이 완료될 때까지 Loading… 화면을 보여주고 싶을 때가 많습니다.
여러 컴포넌트에 로딩 로직을 흩뿌리는 대신, withLoader 라는 HOC에 이를 캡슐화할 수 있습니다.

2.1 최소 골격

function withLoader(
  Element: React.ComponentType,
  url: string
) {
  return (props: Omit) => {
    // implementation will go here
    return ;
  };
}

2.2 전체 구현

import { useEffect, useState } from 'react';

function withLoader(
  Element: React.ComponentType,
  url: string
) {
  return (props: Omit) => {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
      fetch(url)
        .then((res) => res.json())
        .then((json) => {
          setData(json);
          setLoading(false);
        })
        .catch((err) => {
          console.error(err);
          setLoading(false);
        });
    }, [url]);

    if (loading) {
      return Loading…;
    }

    // Pass the fetched data as a `data` prop to the wrapped component
    return ;
  };
}

2.3 컴포넌트에 HOC 적용하기

// CatImages.tsx
type CatImagesProps = {
  data: any; // replace `any` with a proper type if you have one
};

function CatImages({ data }: CatImagesProps) {
  return (
    

      {data.map((cat: any) => (
        
          
        
      ))}
    

  );
}

// Export the wrapped component
export default withLoader(CatImages, 'https://api.thecatapi.com/v1/images/search?limit=10');

이제 CatImagesdata prop을 자동으로 받으며, 요청이 진행 중일 때 사용자는 Loading… 메시지를 보게 됩니다.

3️⃣ 여러 HOC 조합하기

HOC를 스택해서 동작을 결합할 수 있습니다.
아래에서는 hovering prop을 제공하는 withHover HOC를 만든 뒤, 이를 withLoader와 조합합니다.

3.1 withHover HOC

import { useState, useCallback } from 'react';

function withHover(Component: React.ComponentType) {
  return (props: T) => {
    const [hovering, setHovering] = useState(false);

    const onMouseEnter = useCallback(() => setHovering(true), []);
    const onMouseLeave = useCallback(() => setHovering(false), []);

    return (
      
        
      
    );
  };
}

3.2 withHover + withLoader 조합하기

// WrappedCatImages.tsx
export default withHover(
  withLoader(CatImages, 'https://api.thecatapi.com/v1/images/search?limit=10')
);

이제 CatImages 내부에서 withLoader에서 제공되는 datawithHover에서 제공되는 hovering을 모두 사용하여 조건부 “Hovering” 박스를 렌더링할 수 있습니다.

4️⃣ HOC를 Hook(useHover)으로 교체하기

Hooks는 종종 HOC와 동일한 결과를 더 적은 중첩으로 달성할 수 있습니다.

4.1 useHover hook

import { useRef, useState, useEffect, RefObject } from 'react';

function useHover(): [RefObject, boolean] {
  const ref = useRef(null);
  const [hovering, setHovering] = useState(false);

  useEffect(() => {
    const node = ref.current;
    if (!node) return;

    const handleMouseEnter = () => setHovering(true);
    const handleMouseLeave = () => setHovering(false);

    node.addEventListener('mouseenter', handleMouseEnter);
    node.addEventListener('mouseleave', handleMouseLeave);

    return () => {
      node.removeEventListener('mouseenter', handleMouseEnter);
      node.removeEventListener('mouseleave', handleMouseLeave);
    };
  }, []);

  return [ref, hovering];
}

4.2 CatImages 내부에서 Hook 사용하기

// CatImagesWithHook.tsx
import { useLoader } from './useLoader'; // a hook version of the loader logic
import { useHover } from './useHover';

function CatImagesWithHook() {
  const [ref, hovering] = useHover();
  const { data, loading } = useLoader('https://api.thecatapi.com/v1/images/search?limit=10');

  if (loading) return Loading…;

  return (
    
      {hovering && Hovering}
      

        {data.map((cat: any) => (
          
            
          
        ))}
      

    
  );
}

Hook 방식을 사용하면 HOC가 도입하는 추가 컴포넌트 레이어를 없앨 수 있어 컴포넌트 트리를 더 평평하게 유지합니다.

5️⃣ HOC와 Hook을 언제 선호할까

  • HOC횡단 관심사에 적합하며, 이는 어떤 컴포넌트 타입(클래스든 함수든)과도 작동해야 하고, 래핑된 컴포넌트의 API를 변경하지 않으려 할 때 유용합니다.
  • Hook은 일반적으로 상태ful 로직에 대한 최신 선호 솔루션이며, 여러 개의 중첩된 HOC에서 발생할 수 있는 “래퍼 지옥”을 피할 수 있기 때문입니다.

React Docs: “대부분의 경우, Hook만으로 충분하며 트리의 중첩을 줄이는 데 도움이 됩니다.”

6️⃣ 깊게 중첩된 HOC 예시 (예시용)


  
    
      
    
  

동일한 동작은 훅과 컨텍스트 프로바이더를 조합하면 더 깔끔하게 표현할 수 있습니다.

TL;DR

  • HOC는 컴포넌트에 props/로직을 주입합니다 (withStyles, withLoader, withHover).
  • 여러 HOC조합하여 복잡한 동작을 구성할 수 있습니다.
  • (useHover, useLoader)은 대부분의 경우에 대해 현대적이고 중첩이 적은 대안을 제공합니다.

위 스니펫을 프로젝트에 복사해 넣고 필요에 따라 타입을 조정해 사용하세요!

레이아웃 예시

Hook vs. Higher‑Order Component (HOC)

컴포넌트에 직접 훅을 추가하면 더 이상 HOC로 컴포넌트를 감싸야 할 필요가 없습니다.
HOC를 사용하면 동일한 로직을 여러 컴포넌트에 제공하면서 그 로직을 한 곳에 유지할 수 있습니다. 그러나 훅을 사용하면 컴포넌트 내부에서 커스텀 동작을 추가할 수 있어, 여러 컴포넌트가 이 동작에 의존할 경우 HOC 패턴에 비해 버그가 발생할 위험이 증가할 수 있습니다.

Higher‑Order Component에 적합한 사용 사례

  • 해당 컴포넌트가 추가적인 커스텀 로직 없이 독립적으로 동작할 수 있을 때.

Hooks에 적합한 사용 사례

  • 동작이 애플리케이션 전체에 퍼져 있지 않고, 하나 혹은 몇 개의 컴포넌트에서만 사용될 때.
  • 해당 동작이 컴포넌트에 여러 속성을 추가할 때.
Back to Blog

관련 글

더 보기 »

React에서 간단한 Carousel/Slider 만들기

캐러셀 또는 슬라이더는 이미지나 콘텐츠를 하나씩 표시하는 훌륭한 방법입니다. 버튼을 사용하여 이를 탐색할 수 있습니다. 아래는 간단한 구현...

React 서밋 2026

React Summit 2026 커버 이미지 https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.a...