HOC Parttern

1. HOC Parttern 이란?

HOC(Higher Order Component=고차 구성 요소)는 기존 구성 요소에 추가 기능을 추가하는 사용되기에 유용합니다. 컴포넌트를 입력으로 받아, 새로운 컴포넌트를 반환하여 로직을 모듈화 할 수 있습니다.

복잡한 로직을 모듈화하여 중복코드를 줄이고 재사용성을 높일 수 있어서 DRY(Don't-Repeat-Yourself)원칙에 부합한 패턴입니다.

예를 들어 Error텍스트에 항상 동일한 스타일을 적용할때 응용할 수 있습니다.

function errorStyles(Component) {
  return (props) => {
    const style = { color: "#FF2400", fontSize: "1rem" };
    return <Component style={style} {...props} />;
  };
}

const Text = () => <p>Error !</p>;
const ErrorText = errorStyles(Text);

위와 같이 컴포넌트에 적용하면 재사용가능한 추가 구성 요소들을 만들 수 있습니다.

에러 스타일의 공통 부분은 한번에 관리하고, text부분의 컴포넌트를 error페이지 별로 동적으로 구성할 수 있게됩니다.


2. HOC Parttern 예시

  1. case1

조금더 복잡한 로직를 사용할때 유용합니다.

어떠한 제품 데이터를 가져오고, 로딩하고, 에러를 처리하는 컴포넌트가 있습니다.

const Products = ({ hasError, isLoading, data }) => {
  const { products } = data;
  if (isLoading) return <p>Loading…</p>;
  if (hasError) return <p>Sorry, data could not be fetched.</p>;
  if (!data) return <p>No products found.</p>;

  return (
    <ul>
      {products.map((product) => (
        <li key={product.id}>{product.name}</li>
      ))}
    </ul>
  );
};

const { data, loading, error } = fetchData();
<Products {...{ data, error }} isLoading={loading} />;

이때 제품의 여러가지이고 각 제품들은 화면을 구성하는 UI가 다르지만, 동일하게 데이터를 가져오고 로딩하고 에러를 처리를 해야한다면 다음과 같이 HOC 패턴을 사용할 수 있습니다.

  1. case 2

그리고 사용자의 개인 정보와 같이 민감한 데이터를 표시하는 경우 사용할 수 있습니다.

특정 페이지에 접근할때, 인증이 되어 있어야한다 했을때 각 페이지 별로 인증 확인 기능을 만드는 것보다 HOC패턴으로 래핑하여 간단하게 처리할 수 있습니다.

// 인증 HOC 컴포넌트
const withAuthentication = (WrappedComponent, authenticationMethod) => {
  return (props) => {
    const [isAuthenticated, setIsAuthenticated] = useState(false);
    const [isAuthenticating, setIsAuthenticating] = useState(true);
    const [authError, setAuthError] = useState(null);

    useEffect(() => {
      setTimeout(() => {
        if (
          authenticationMethod === "OTP" ||
          authenticationMethod === "PIN" ||
          authenticationMethod === "PASSWORD"
        ) {
          setIsAuthenticated(true);
          setIsAuthenticating(false);
        } else {
          setAuthError("Invalid authentication method");
          setIsAuthenticating(false);
        }
      }, 1000);
    }, [authenticationMethod]);

    if (isAuthenticating) {
      return <div>Authenticating...</div>;
    }

    if (authError) {
      return <div>Error: {authError}</div>;
    }

    if (!isAuthenticated) {
      return <div>Please authenticate to view this content</div>;
    }

    return <WrappedComponent {...props} />;
  };
};

// 인증이 필요한 컴포넌트
const UserProfile = () => (
  <div>
    <h2>User Profile</h2>
  </div>
);

const UserProfileWithAuthentication = withAuthentication(UserProfile, "OTP");

// 다음과 같이 인증 확인 적용된 UserProfile 사용할수 있습니다.
<UserProfileWithAuthentication />;

이렇게 하면 독립적으로 인증 시스템을 유지 보수 할수 있게 되니다.


3. 결론

컴포넌트간의 결합도를 줄이며, 독립적으로 작동하게 설계할 수 있습니다. 다만 중첩해서 사용해야하는경우 (withAuth(withLogger(withData(Component))) 코드가 너무 복잡해지는 단점도 있기는 합니다.

컴포넌트 자체에서 모듈을 분리하는 방식은 커스텀훅이 좀더 가독성과 유지보수성에서 좋은것 같긴하지만, 기존의 컴포넌트에 추가 기능을 부모 컴포넌트에서 확장하고자 할때 유용하게 사용하고 있습니다.



reference

[1] hoc-pattern” https://www.patterns.dev/react/hoc-pattern
[2] “React Design Patterns: Higher Order Components Pattern” 2023.11.26 https://medium.com/@vitorbritto/react-design-patterns-higher-order-components-pattern-421e7f1edcfa