[FE 디자인패턴 스터디] HOC 패턴
![[FE 디자인패턴 스터디] HOC 패턴](/_next/image?url=https%3A%2F%2Fvelog.velcdn.com%2Fimages%2Fhayou%2Fpost%2F92be4881-6725-4589-ad71-9fce7213f034%2Fimage.webp&w=3840&q=75)
1. 패턴 정의: 정의와 핵심 요약
- HOC는 기존 컴포넌트를 인자로 받아, 추가 로직을 덧붙인 새로운 컴포넌트를 반환하는 패턴이다.
- 여러 컴포넌트에서 반복되는 로직을 한 곳에 모아, 재사용성을 높이고 관심사를 분리할 수 있다.
2. 사용 목적: 이 패턴이 필요한 이유
- 중복된 로직(스타일링, 권한 처리, 로딩 등)을 여러 컴포넌트마다 작성하지 않고 한 곳에서 관리하고 싶을 때 사용한다.
- 원본 컴포넌트의 내부 코드를 수정하지 않고도, 새로운 기능이나 데이터 소스를 주입하고 싶을 때 사용한다.
3. 패턴 설명: 동작 방식과 구성 요소

구성요소
- HOC 함수: 원본 컴포넌트를 받아 새로운 컴포넌트를 반환하는 함수.
- Wrapped Component: HOC에 의해 감싸지는 원본 컴포넌트.
동작 방식
- HOC 함수는 원본 컴포넌트를 받아 새로운 컴포넌트를 반환한다.
- 반환된 컴포넌트 내부에서 공통 로직을 실행하거나, 추가 props를 주입한 뒤 원본 컴포넌트를 렌더링한다.
- 원본 컴포넌트는 이를 “주입받은 props”를 통해 마치 자신의 로직처럼 활용한다.
대표적인 HOC 예시
React.memo: props 변경 여부를 기준으로 불필요한 리렌더링을 방지withAuthGuard: 인증/권한 없는 사용자의 접근 차단withErrorBoundary: 렌더링 에러를 잡고 안전한 UI로 대체
장단점
장점
- 코드 중복 제거, 재사용성 향상
- 관심사 분리: UI 컴포넌트와 로직/데이터 처리 로직 분리 가능
단점
- props 이름 충돌 위험
- 여러 HOC를 중첩하면 디버깅이 어려워짐 (“Wrapper Hell”)

4. 코드 및 활용 예시
React.memo
import React from "react";
type UserCardProps = { name: string; age: number };
function UserCard({ name, age }: UserCardProps) {
console.log("render:", name);
return (
<div>
{name} ({age})
</div>
);
}
export const MemoUserCard = React.memo(UserCard);
<MemoUserCard name="Alice" age={30} />
withAuthGuard
import React from "react";
import { Navigate } from "react-router-dom";
function useAuth() {
const user = { id: "123", role: "user" } as null | {
id: string;
role: string;
};
return { user };
}
type AnyComp = React.ComponentType<any>;
export function withAuthGuard<ComponentProps>(Wrapped: AnyComp) {
const Guard: React.FC<ComponentProps> = (props) => {
const { user } = useAuth();
if (!user) return <Navigate to="/login" replace />;
return <Wrapped {...props} />;
};
Guard.displayName = `withAuthGuard(${
Wrapped.displayName || Wrapped.name || "Component"
})`;
return Guard;
}
const SettingsPage = () => <div>Settings</div>;
export default withAuthGuard(SettingsPage);
withErrorBoundary
import React from "react";
// Error Boundary는 클래스 컴포넌트여야 렌더 에러를 잡을 수 있음
class ErrorBoundary extends React.Component<
{ fallback?: React.ReactNode },
{ hasError: boolean }
> {
constructor(props: any) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error: any, info: any) {
console.error("Caught by ErrorBoundary:", error, info);
}
render() {
if (this.state.hasError)
return this.props.fallback ?? <div>Something went wrong.</div>;
return this.props.children;
}
}
type AnyComp = React.ComponentType<any>;
export function withErrorBoundary<ComponentProps>(
Wrapped: AnyComp,
fallback?: React.ReactNode
) {
const WithBoundary: React.FC<ComponentProps> = (props) => (
<ErrorBoundary fallback={fallback}>
<Wrapped {...props} />
</ErrorBoundary>
);
WithBoundary.displayName = `withErrorBoundary(${
Wrapped.displayName || Wrapped.name || "Component"
})`;
return WithBoundary;
}
const Crashy = () => {
throw new Error("boom");
};
export default withErrorBoundary(Crashy, <div>⚠️ 에러</div>);
5. 정리와 확장: 학습 포인트와 추가 학습거리
학습 포인트
- HOC는 컴포넌트를 인자로 받아 새 컴포넌트를 반환하는 패턴으로, 공통 로직을 재사용하고 관심사를 분리하는 데 유용하다.
- 훅 등장 이후, 단순한 “데이터 주입/상태 연결” 목적의 HOC는 훅으로 대체되는 추세다.
- 다만 “원본 컴포넌트를 수정하지 않고 감싸야 하는” 요구가 있을 때 HOC의 장점이 살아난다.
추가 학습: Container–Presentational 패턴과 HOC의 차이
성격 차이
- Container–Presentational: “데이터/비즈니스 로직”과 “UI 표현”을 분리하자는 설계 원칙
- HOC: 위 원칙(또는 다른 관심사 분리)을 구현하는 기법 중 하나
구현 방법 다양성
- 동일한 원칙을 HOC뿐 아니라 커스텀 훅 + 얇은 컨테이너, 또는 Render Props로도 구현 가능
권장 흐름
- 데이터/비즈니스 로직은 커스텀 훅으로 캡슐화 → 화면에서는 컨테이너가 훅을 호출해 프리젠테이셔널 컴포넌트에 props로 전달
- HOC는 “프로젝트 전반의 기존 컴포넌트들을 수정 없이 공통 규칙으로 감싸야 할 때” 선택
추가 학습: 훅으로 대체가 어려운 HOC
Error Boundary(렌더링 에러 포착)
- 현재 React에선 컴포넌트 경계가 필요하며, 훅만으로 렌더 단계 에러를 잡을 수 없다.
- withErrorBoundary(Component) 형태의 HOC/래퍼가 실용적이다.
강제 래핑이 필요한 경우
- 원본을 수정하지 않고 전역적으로 Provider/레이아웃/데코레이터를 적용해야 할 때(테마, 추적, 접근 가드 등).
- 훅은 컴포넌트 내부에서만 호출 가능하므로, 외부에서 일괄 적용하려면 HOC가 적합하다.
comments
loading…