React 권한 분기와 이벤트 핸들링, HOC와 HOF 개념 정리

Kingmo·2022년 4월 17일
post-thumbnail

HOC는 Higher Order Component,
HOF는 Higher Order Function으로
두 개념은 클로저로부터 확장된 개념이다.

HOC (Higher-Order Component)

HOC는 컴포넌트를 인자로 받아 새로운 기능이 주입된 컴포넌트를 반환하는 함수다.

로그인 여부를 검사하는 권한 분기 로직을 개별 페이지마다 useEffect로 작성하면, 정책이 바뀔 때마다 모든 페이지를 찾아다니며 수정해야 한다. HOC를 활용하면 인증 검사 로직을 한곳에서 공통으로 관리할 수 있다.

1. withAuth 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} />;
};

2. 화살표 함수 분해해서 이해하기

위의 한 줄짜리 화살표 함수((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} />;
  };
};

3. 보호가 필요한 페이지에 적용

// 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 (Higher-Order Function)

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>;
}
  • HTML id 속성은 DOM 전역에서 고유해야 하므로 네이밍 충돌 위험이 있다.
  • 이벤트 버블링으로 인해 <button id="123"><span>텍스트</span></button> 구조에서 span이 클릭되면 event.target.id가 빈 문자열로 읽힐 수 있다.

HOF를 통한 개선

//  HOF 적용
export default function List() {
  // 바깥 함수는 인자를 받아 클로저로 기억하고, 이벤트 핸들러 함수를 반환한다.
  const onClickItem = (id) => (event) => {
    console.log("선택된 ID:", id);
  };

  // 실행 시점에 id가 바인딩된 핸들러 함수를 onClick에 전달
  return <button onClick={onClickItem(123)}>클릭</button>;
}

HOF 동작 원리

onClick={onClickItem(123)}이 렌더링 시점에 즉시 실행되면서 내부의 (event) => { console.log(123); } 함수를 반환한다.

결과적으로 이벤트 리스너에는 123이라는 식별자를 클로저로 캡처해 둔 온전한 핸들러 함수가 바인딩되므로, HTML 태그에 억지로 id나 data-* 속성을 주입하지 않고도 안전하게 데이터를 넘길 수 있다.

핵심 요약

구분HOC (Higher-Order Component)HOF (Higher-Order Function)
반환값새로운 React 컴포넌트 (JSX)일반 함수
주요 목적인증 체크, 로깅 등 페이지/컴포넌트 공통 로직 재사용이벤트 핸들러 인자 전달, 함수 합성, 커링(Currying)
핵심 원리클로저(Closure)를 통한 상위 스코프 인자 캡처클로저(Closure)를 통한 상위 스코프 인자 캡처
  • 최신 React에서는 공통 로직 재사용을 위해 Custom Hook을 사용하는 추세지만, 특정 라우트나 컴포넌트 마운트 자체를 원천 차단하는 가드(Guard) 목적의 HOC는 여전히 유용하다.
profile
Developer

2개의 댓글

comment-user-thumbnail
2022년 4월 17일

잘보고 갑니다

1개의 답글