본문 바로가기
FE/React

HOC & HOF

by ideal_string 2022. 10. 19.

고차컴포넌트(HOC, High Order Component)와 고차함수(HOF, High Order Function). 일단 둘 다 dlfma 뜻대로 작동한다. 높은 순위로 작동하는 컴포넌트와 함수다. 이런 게 왜 필요한가 했는데, 사용하다보니 필요성이 느껴진다. 더 신기한건 이런 개념을 react 문서에서도 기술하고 있다는 점이다.

 

클래스 컴포넌트든, 함수 컴포넌트든 마이페이지를 만든 후 이런 생각이 문득 든다. ‘마이페이지를 들어왔을 때 로그인 상태가 아니라면 경고창을 띄우고 돌려보내는 기능을 넣고 싶다’고. 그런데 가만히 살펴보니 다른 페이지에서도 로그인이 안되었을 때 들어가지 못하게 하는 점검하는 코드가 여러 곳에서 필요함을 느낀다. ‘이를 모아 한번에 묶으면 좋을텐데…’라고 생각이 드는건 당연하다. 이런 흐름이 HOC와 HOF를 나타나게 했다.

 

리액트 기술문서 : 고차 컴포넌트(HOC, Higher Order Component)는 컴포넌트 로직을 재사용하기 위한 React의 고급 기술입니다. 고차 컴포넌트(HOC)는 React API의 일부가 아니며, React의 구성적 특성에서 나오는 패턴입니다.

 

리액트 고급 안내서에서도 고차컴포넌트를 설명하듯, HOC는 특정 기능, 혹은 API가 아니다. React의 특성을 활용한 기술일 뿐이다. 리액트에서 HOC만 기술한 이유는 문서가 클래스 컴포넌트를 주로 사용할 당시에 만들어졌기 때문이고, 이 개념만 이해하더라도 HOF를 사용하는데 문제 없기 때문이다. 단, HOC를 이해하려면 클로저를 이해해야 한다.

 

HOC

리액트에서 porps를 통해 자식 컴포넌트로 원하는 값을 넘겨주고 있다. 이때 값으로 함수를 넘긴다면? 바로 이 지점이 HOC다.

 

//withAuth file
…

export const withAuth = (Component: any) => (props: any) => {
  const router = useRouter();
  useEffect(() => {
    if (!localStorage.getItem("accessToken")) {
      alert("로그인 후 이용 가능!");
      void router.push("/login_check_hoc");
    }
  }, []);

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

 

위 코드는 페이지가 로드될 시  localstorage에 있는 값으로 로그인 여부를검증하는 코드다. 같은 코드를 여러 코드에 중복 코딩하지 않고, 한 곳에 라이브러리처럼 만든 셈. 컴포넌트를 인자로 받고, 바로 리턴하는데, 그 전에 위 코드에 적힌 부분을 먼저 실행한다.

//mypage
…

unction LoginSuccessPage() {
…
  return (
    <>
      <div>{data?.fetchUserLoggedIn.name}님 환영합니다.</div>
      <div>{data?.fetchUserLoggedIn.email}</div>
    </>
  );
}
export default withAuth(LoginSuccessPage);

 

export를 보면 LoginSuccessPage를 withAuth로 감쌌다. LoginSuccessPage를 실행하기 전 withAuth가 먼저 있기에 withAuth를 실행하러 들어가고, 해당 코드를 읽은 후 return을 따라 LoginSuccessPage를 실행한다.

해당 페이지 컴포넌트보다 앞에 선언한 더 먼저 실행되는 것을 볼 수 있다. 이게 바로 고차컴포넌트, 즉  HOC다.

 

HOF

고차함수는 개념이 훨씬 더 쉽고 간편하다. 컴포넌트 단위가 아닌 함수단위다. 보통 특정 기능을 담당하는 커스텀 훅을 주로 사용한다. HOF도 위에서 사용한 것처럼 로그인 권한분기를 통해 예를 들었다.

 

//mypage
…

unction LoginSuccessPage() {
useAuth()

…
  return (
    <>
      <div>{data?.fetchUserLoggedIn.name}님 환영합니다.</div>
      <div>{data?.fetchUserLoggedIn.email}</div>
    </>
  );
}
export LoginSuccessPage;
//useAuth
…
export default function useAuth() {
  const router = useRouter();
  useEffect(() => {
    if (!localStorage.getItem("accessToken")) {
      alert("로그인 후 이용 가능!");
      void router.push("/login_check_hof);
    }
  }, []);
}

 

useAuth와 mypage 파일을 살펴보면 HOC 파일과 크게 다르지 않다. 다른 점은 mypage에서 export 시 컴포넌트를 별도로 감싸지 않고 페이지 내 useAuth를 썼다. useAuth 파일은 인자값을 받지도 않고 리턴 값 없이 useEffect만 코딩했다. 

이 코드 또한 리액트, 즉 자바스크립트의 속성을 활용했다. 순차적으로 읽어가는 특성상 맨 위에 있는 코드를 읽을 것이고, 처음 읽은 코드가 useAuth 함수다. 함수에 들어가서 관련 사항을 체킹하고, 함수는 그에 따른 여부를 답해줬을 뿐이다. 그 여부가 로그인 되어있지 않다면 로그인하도록 안내하는 것.

 

마무리

결국 HOC와 HOF는 그 어떤 것보내 언급한 컴포넌트모다 먼저 실행시킬 무언가를 정하는 기능이다. 단지, 먼저 실행 시키는 함수를 컴포넌트를 포함한 값으로 쓸 것인지, 함수로 쓸 것인지가 다르다. 컴포넌트를 매개변수로 넘기는 HOC는 클래스컴포넌트 사용할 시 주로 쓰였고, 요즘엔 HOF를 주로 쓴다고 한다. 아무래도 사용하기가 더 간편하기 때문인 듯하다.

여기서 파일명 관례가 하나가 있다. 컴포넌트를 매개변수로 넘길 때 HOC 경우 ‘with’라는 단어를 앞에 붙이고, hooks이 포함되어 리렌더링을 발생시키는 HOF는 ‘use’라는 이름을 파일명과 함수명에 붙여 놓는다고 한다. 코드를 한 눈에 알아차리기 쉽도록 정한 관례다. 이 때문에 프로젝트 내 단순 library로 만든 dateformatter 같은 것과는 그 성격이 다르다.

※ 잘못된 내용이 있을 경우 댓글로 알려주세요. 배우고 익히고 수정하겠습니다:)

'FE > React' 카테고리의 다른 글

Shallow Routing  (0) 2022.10.25
LazyLoad & PreLoad  (0) 2022.10.24
전역 상태 관리  (0) 2022.10.09
Lifecycle(feat. DOM)  (0) 2022.10.03
Class Component & Functional Component  (0) 2022.10.01