[React] HOC vs Hook

원준·2023년 12월 27일

React

목록 보기
1/2
post-thumbnail

이번에 진행한 프로젝트에서 렌더링 최적화를 하기 위해 React.memo와 useMemo를 공부했다.
그런데 그 차이점이 React.memo는 HOC이고 useMemo는 Hook이라는 설명이 많았다...

리액트를 이번에 처음 사용해 본 것이라 Hook은 무엇인지 알고 있었지만,
HOC에 대해서는 잘 알지도 못했고 이 둘이 어떤 차이가 있는지도 잘 몰랐다...

그래서 이번 기회를 통해 HOC와 Hook은 정확히 뭐고 어떤 차이가 있는지 알아보기로 했다.


1. HOC

HOC란 말 그대로 컴포넌트를 받아 새로운 컴포넌트를 반환하는 고차 컴포넌트를 의미한다.
컴포넌트를 재사용하기 위해서 사용하며, 코드의 재사용성을 높여주는 효과를 가진다.

1) HOC의 장점

class Body extends React.Component {
  mount() {
    console.log(`마운트`)
  }

  render() {
    return <div>Body</div>
  }
}
class Button extends React.Component {
  mount() {
    console.log(`마운트`)
  }

  click = () => {
    console.log(`버튼 클릭`)
  }

  render() {
    return <button onClick={this.click}>클릭</button>
  }
}

우선 다음과 같이 마운트 될 때 알림을 해주는 2개의 코드가 있다고 가정하겠다.

이 코드에는 크게 두 가지의 문제가 있는데, 1번째는 두 코드가 중복된 코드를 가진다는 점이고
2번째는 로그를 남기는 것이 각 컴포넌트의 주요한 역할이 아니라는 점이다.

const withLogging = (component) => {
  class WithLogging extends React.Component {
    render() {
      const enhancedProps = { log }

      return <component {...this.props} {...enhancedProps} />
    }

    mount() {
      this.log('마운트')
    }
  }

  const log = (message) => {
    console.log(message)
  }
  
  return WithLogging
}
const Body = () => <div>Body</div>

const Button ({ log }) => {
  const handleClick = () => log("버튼 클릭");

  return <button onClick={handleClick}>클릭</button>;
}

const EnhancedHeader = withLogging(Body)
const EnhancedButton = withLogging(Button)

그런데 다음과 같이 HOC를 사용하게 되면, 앞에서 말했던 문제가 전부 해결되게 된다.

반복되는 코드가 빠져 코드가 보기 쉬워지고 재사용성이 늘어나며,
각 컴포넌트는 다른 것을 할 필요 없이 자신에게 주어진 역할만 수행할 수 있게 된다.

2) HOC의 단점

마우스의 위치, 창의 크기, 사용자의 위치 정보 등이 필요한 컴포넌트가 있다고 가정해보자.

const HOCHell = () => {
  return (
    <HOC1>
      <HOC2>
        <HOC3>
            <Component />
        </HOC3>
      </HOC2>
    </HOC1>
  );
};

그리고 이를 HOC를 사용해 구현하게 되면 다음과 같아지는데, 가독성이 안좋아진다.

이렇게 불필요하게 깊이가 깊어진다는 단점 이외에도 항상 함수로 감싸줘야 한다거나,
사용자가 넘기지 않은 암묵적인 속성값이 넘어간다거나 (this.props.dispatch 등)
넘어가는 속성값의 이름이 모두 달라야 하는 등의 단점이 있다.

2. Hook

리액트의 컴포넌트는 클래스형 컴포넌트와 함수형 컴포넌트로 나뉜다.
그런데 클래스형 컴포넌트구성이 어렵고 컴포넌트의 재사용성이 떨어진다는 단점이 있다.
따라서 리액트에서도 함수형 컴포넌트를 사용하도록 권장하고 있는데,
이 때 Hook함수형 컴포넌트클래스형 컴포넌트의 기능을 사용할 수 있도록 해준다.

1) Hook의 장점

const HOCHell = () => {
  const mousePosition = useMousePosition()
  const windowSizes = useWindowSize()
  const userLocation = useUserLocation()
  
  return (
    <Component
      mousePosition={mousePosition}
      windowSizes={windowSizes}
      userLocation={useLocation}
    />
  )
}

이전 HOC의 단점에서 본 코드를 다음과 같이 Hook을 사용하는 코드로 바꿀 수 있다.

이처럼 깊이가 깊어지거나 함수로 감싸야 하는 등의 단점이 사라져 코드가 간결해지며,
암묵적인 속성값을 신경쓰거나 속성값의 이름을 신경쓰지 않아도 된다는 장점이 있다.

2) Hook의 단점

버튼을 누르면 사용자의 나이에 1을 더해주는 컴포넌트가 있다고 가정해보자.

const Component() => {
  const userData = { name: 'John', age: 25 };

  useEffect(() => {
    console.log('Updated user data:', userData);
  }, [userData]);

  const updateUserData = () => {
    userData.age++;
  };

  return (
    <div>
      <div>User Data: {JSON.stringify(userData)}</div>
      <button onClick={updateUserData}>Update User Data</button>
    </div>
  );
}

그런데 이를 다음과 같이 Hook을 사용해 구현하면, 버튼을 눌러도 화면이 변하지 않게 된다.

이는 useEffect의 의존성 배열이 참조형 데이터를 받게 되면,
주소가 변경될 때 useEffect가 동작되는데 위의 코드는 주소의 변경이 없기 때문이다.

이처럼 useEffect에 빈틈이 있다는 단점 외에도 리액트 함수 내에서만 사용이 가능하고
호출되는 순서에 의존하기 때문에 항상 최상단에서 Hook을 사용해야 하며,
클로저에 의존적이기 때문에 컴포넌트가 커질수록 복잡도가 급격히 커지는 등의 단점이 있다.

3. HOC vs Hook

앞에서 본 것처럼 HOC와 Hook은 모두 컴포넌트의 재사용성을 늘려준다는 공통점이 있다.

그러나 HOC는 주로 클래스형 컴포넌트에서 사용되며, Hook은 함수형 컴포넌트에서 사용되고
또한, HOC는 컴포넌트를 받아 컴포넌트를 반환하는 고차 컴포넌트이지만,
Hook은 함수형 컴포넌트를 클래스형 컴포넌트처럼 사용하게 해주는 함수라는 차이점이 있다.


참고 자료

0개의 댓글