본문으로 바로가기

[React] 커스텀훅 vs 고차 컴포넌트

category 1. 웹개발/1_1_5 React JS 2024. 4. 27. 11:30

 

 

일반적인 자바스크립트에서 재사용 로직을 작성하는 방식 외에도 리액트에서는 재사용할 수 있는 로직을 관리할 수 있는 두 가지 방법이 있습니다. 바로 커스텀훅(custom hook)과 고차 컴포넌트(higher order component)입니다. 커스텀 훅과 고차 컴포넌트가 무엇이며 어떻게, 언제 쓰이는지 알아보도록 하겠습니다.

 

 

커스텀 훅(custom hook)

서로 다른 컴포넌트 내부에서 같은 로직을 공유하고자 할 때 주로 사용되는 것입니다. 고차 컴포넌트의 경우 리액트가 아닌 곳에서도 사용이 가능하지만, 커스텀 훅은 오로지 리액트에서만 사용할 수 있는 방식입니다.

 

이러한 커스텀 훅의 규칙 중 하나는 이름이 반드시 use로 시작하는 함수를 만들어야 합니다.

만약 규칙을 따르지 않는다면 다음과 같이 에러가 발생합니다. 아래의 코드를 통해 확인해보겠습니다.

function result() {
  // React Hook "useState" is called in function "useook" that is neither a React function component nor a custom React Hook function. 
  // React component names must start with an uppercase letter. 
  // React Hook names must start with the word "use".eslint
  const [result, setResult] = useState();
}

React Hook "useState" is called in function "hook" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".

use로 만들지 않은 커스텀 훅 함수 내부에서 useState를 사용하려고 하였더니 에러가 발생하였습니다. react-hooks/rules-of-hooks가 지적하는 바는 훅은 함수 컴포넌트 내부 또는 커스텀 훅 내부에서만 사용할 수 있다는 것입니다. 이 경고를 제거하는 방법은 커스텀 훅 함수명에 use를 추가하시면 됩니다.

 

 

고차 컴포넌트(HOC, Higher Order Component)

컴포넌트 자체의 로직을 재사용하기 위한 방법입니다. 고차 함수의 일종으로, 자바스크립트의 일급 객체, 합수의 특징을 이용하므로 리액트가 아니더라도 다른 자바스크립트 환경에서도 널리 사용됩니다. 리액트에서 사용되는 대표적인 예시는 React API인 React.memo이지만, 너무 흔한 예시이니 다른 코드로 확인해 보겠습니다.

function withLoginComponent(component) {
  return function (props) {
    const { loginRequired, ...restProps } = props;

    if (loginRequired) {
      return <>로그인이 필요합니다.</>
    }

    return <Component {...(restProps)} />
  }
}

// withLoginComponent로 감싸기.
// 로그인 여부, 로그인이 안되면 다른 컴포넌트를 렌더링하는 책임은 모두
// 고차 컴포넌트인 withLoginComponent에 맡길 수 있어 편리
const Component = withLoginComponent((props) => {
  return <h1>{props.value}</h1>
})

 

<Component/>는 평범한 컴포넌트지만, 이 함수 컴포넌트 자체를 <withLoginComponent/>라 불리는 고차 컴포넌트로 감싸줬습니다. withLoginComponent는 함수를 인수로 받으며, 컴포넌트를 반환하는 고차 컴포넌트입니다. 

 

이처럼 고차 컴포넌트는 컴포넌트 전체를 감쌀 수 있다는 점에서 커스텀 훅보다는 더욱 큰 영향력을 컴포넌트에 미칠 수 있습니다. 단순히 값을 반환하거나 부수 효과를 실행하는 커스텀 훅과는 다르게, 고차 컴포넌트는 컴포넌트의 결과물에 영향을 미칠 수 있는 다른 공통된 작업을 처리할 수 있습니다.

 

커스텀 훅에서는 use로 시작하는 이름을 사용했다면, 고차 컴포넌트는 with로 시작하는 이름을 사용해야 합니다. 이는 앞선 use와 다르게 ESLint 규칙 등으로 강제되는 사항은 아니지만 리액트 라우터의 withRouter와 같이 리액트 커뮤니티에 널리 퍼진 일종의 관습이라고 볼 수 있습니다. with가 접두사로 붙어있으면 고차 컴포넌트임을 개발자는 손쉽게 알아챌 수 있습니다.

 

고차 컴포넌트를 사용할 때 주의할 점 중 하나는 부수 효과를 최소화해야 한다는 것입니다. 고차 컴포넌트는 반드시 컴포넌트를 인수받게 되는데, 반드시 컴포넌트의 props를 임의로 수정, 추가, 삭제하는 일은 없어야 합니다. 또한, 여러 개의 고차 컴포넌트를 감쌀 경우 복잡성이 커진다는 것입니다. 고차 컴포넌트가 증가할수록 결과를 예측하기 어려워집니다. 다음 코드를 보시면 바로 이해가 될 겁니다.

const Component = withLoginComponent1(
  withLoginComponent2(
    withLoginComponent3(
      withLoginComponent4(
        withLoginComponent5(() => {
          return <>고차 컴포넌트로 복잡해진 코딩병원..</>
        })
      )
    )
  )
)

 

 

 

그러면 커스텀 훅과 고차 컴포넌트 중 무엇을 써야 할까?

1. 커스텀 훅이 필요한 경우

단순히 useEffect, useState와 같이 리액트에서 제공하는 훅으로만 공통 로직을 격리할 수 있다면 커스텀 훅을 사용하는 것이 좋습니다. 커스텀훅은 그 자체로는 렌더링에 영향을 미치지 못하기 때문에 사용이 제한적입니다. 따라서 컴포넌트 내부에 미치는 영향을 최소화해 개발자가 훅을 원하는 방향으로만 사용할 수 있다는 장점이 있습니다. 다음 코드를 통해 확인해 보겠습니다.

function HookComponent() {
  const { loggedIn } = useLogin();

  useEffect(() => {
    if (!loggedIn) {
      // .. do somthing
    }
  }, [loggedIn]);
}

const HOCComponent = withLoginComponent(() => {
  // .. do somthing
})

 

로그인 정보를 가지고 있는 훅인 useLogin은 단순히 loggedIn에 대한 값만 제공할 뿐, 이에 대한 처리는 컴포넌트를 사용하는 쪽에서 원하는 대로 사용 가능합니다. 따라서 부수 효과가 비교적 제한적이라고 볼 수 있습니다. 반면 withLoginComponent는 고차 컴포넌트가 어떤 일을 하는지, 어떤 결과물을 반환할지는 직접 보기 전까지 확인하기 어려워 예측하기가 어렵습니다. 따라서 단순히 컴포넌트 전반에 걸쳐 동일한 로직으로 값을 제공하거나 특정한 훅의 작동을 취하게 하고 싶다면 커스텀 훅을 사용하는 것이 좋습니다.

 

2. 고차 컴포넌트를 사용해야 하는 경우

함수 컴포넌트의 반환값, 즉 렌더링의 결과물에도 영향을 미치는 공통 로직이라면 고차 컴포넌트를 사용하면 됩니다. 다음 코드는 위의 예제를 살짝 바꿔봤습니다.

function HookComponent() {
  const { loggedIn } = useLogin();

  useEffect(() => {
    if (!loggedIn) {
      // .. do somthing
    }
  }, [loggedIn]);
}

const HOCComponent = withLoginComponent(() => {
  // loggedIn 값을 신경 쓰지 않고 그냥 컴포넌트에 필요한 로직만
  // 추가해서 간단해졌다. loggedIn에 따른 제어는 고차 컴포넌트에서 진행한다.
  return <>안녕하세요.</>
})

 

커스텀 훅은 해당 컴포넌트가 반환하는 렌더링 결과물에까지 영향을 미치기는 어렵습니다. 이러한 중복처리가 해당 커스텀 훅을 사용하는 애플리케이션 전반에 걸쳐 나타나게 될 것이므로 커스텀 훅보다는 고차 컴포넌트를 사용하는 것이 좋습니다.

 

 

 

References

https://ko.legacy.reactjs.org/docs/hooks-custom.html

https://ko.legacy.reactjs.org/docs/higher-order-components.html

React Deep Dive