HOC는 Higher Order Component,
HOF는 Higher Order Function으로
두 개념은 클로저로부터 확장된 개념이다.
HOC는 컴포넌트를 인자로 받아 새로운 기능이 주입된 컴포넌트를 반환하는 함수다.
로그인 여부를 검사하는 권한 분기 로직을 개별 페이지마다 useEffect로 작성하면, 정책이 바뀔 때마다 모든 페이지를 찾아다니며 수정해야 한다. HOC를 활용하면 인증 검사 로직을 한곳에서 공통으로 관리할 수 있다.
// withAuth.js
import { useRouter } from "next/router";
import { useEffect } from "react";
export const withAuth = (Component) => (props) => {
const router = useRouter();
useEffect(() => {
// 토큰이 없으면 로그인 페이지로 리다이렉트
if (!localStorage.getItem("accessToken")) {
alert("로그인 후 이용 가능합니다.");
router.push("/login");
}
}, []);
// 인증 검사를 통과하면 원래 컴포넌트를 그대로 렌더링
return <Component {...props} />;
};
위의 한 줄짜리 화살표 함수((Component) => (props) => ...)는 일반 함수 형태로 풀어서 보면 구조가 명확해진다.
export const withAuth = (Component) => {
// 클로저를 통해 Component를 기억하는 새로운 함수 컴포넌트를 반환
return function AuthenticatedComponent(props) {
const router = useRouter();
useEffect(() => {
if (!localStorage.getItem("accessToken")) {
alert("로그인 후 이용 가능합니다.");
router.push("/login");
}
}, []);
return <Component {...props} />;
};
};
// LoginSuccessPage.js
import { gql, useQuery } from "@apollo/client";
import { withAuth } from "./withAuth";
const FETCH_USER_LOGGED_IN = gql`
query fetchUserLoggedIn {
fetchUserLoggedIn {
email
name
}
}
`;
function LoginSuccessPage(props) {
const { data } = useQuery(FETCH_USER_LOGGED_IN);
return <div>{data?.fetchUserLoggedIn.name}님 환영합니다!</div>;
}
// 컴포넌트를 withAuth로 감싸서 export
export default withAuth(LoginSuccessPage);
LoginSuccessPage가 곧바로 실행되는 대신, HOC가 반환한 래퍼 컴포넌트가 먼저 마운트된다.HOF는 함수를 인자로 받거나 또 다른 함수를 반환하는 함수다. HOC 역시 반환값이 컴포넌트(JSX)일 뿐, 본질적으로는 HOF에 속한다.
실무에서 HOF는 주로 이벤트 핸들러에 추가 인자를 전달할 때 요긴하게 사용된다.
event.target.id)// ❌ 기존 방식
export default function List() {
const onClickItem = (event) => {
// string으로 자동 형변환되며, 자식 태그 클릭 시 id가 누락될 위험이 있다.
console.log(event.target.id);
};
return <button id="123" onClick={onClickItem}>클릭</button>;
}
id 속성은 DOM 전역에서 고유해야 하므로 네이밍 충돌 위험이 있다.<button id="123"><span>텍스트</span></button> 구조에서 span이 클릭되면 event.target.id가 빈 문자열로 읽힐 수 있다.// HOF 적용
export default function List() {
// 바깥 함수는 인자를 받아 클로저로 기억하고, 이벤트 핸들러 함수를 반환한다.
const onClickItem = (id) => (event) => {
console.log("선택된 ID:", id);
};
// 실행 시점에 id가 바인딩된 핸들러 함수를 onClick에 전달
return <button onClick={onClickItem(123)}>클릭</button>;
}
onClick={onClickItem(123)}이 렌더링 시점에 즉시 실행되면서 내부의 (event) => { console.log(123); } 함수를 반환한다.
결과적으로 이벤트 리스너에는 123이라는 식별자를 클로저로 캡처해 둔 온전한 핸들러 함수가 바인딩되므로, HTML 태그에 억지로 id나 data-* 속성을 주입하지 않고도 안전하게 데이터를 넘길 수 있다.
| 구분 | HOC (Higher-Order Component) | HOF (Higher-Order Function) |
|---|---|---|
| 반환값 | 새로운 React 컴포넌트 (JSX) | 일반 함수 |
| 주요 목적 | 인증 체크, 로깅 등 페이지/컴포넌트 공통 로직 재사용 | 이벤트 핸들러 인자 전달, 함수 합성, 커링(Currying) |
| 핵심 원리 | 클로저(Closure)를 통한 상위 스코프 인자 캡처 | 클로저(Closure)를 통한 상위 스코프 인자 캡처 |
잘보고 갑니다