HOC

담당
김준표
완료
완료
유형
React
비고

요약

HOC란, React의 고차 컴포넌트로 컴포넌트를 전달받아 새로운 컴포넌트를 반환하는 함수이다.
클래스형 컴포넌트에서 공통된 로직을 분리하여 전달받은 각각의 컴포넌트에 해당 로직을 적용하는 형태로 구현한다.
하지만, React가 함수형 컴포넌트를 장려하며 React 16.8 에서 추가된 Hooks 로 인해 현재는 클래스형 컴포넌트로 구성된 코드나 Hooks가 처리할 수 없는 특별한 경우에만 사용한다.

HOC(Higher-Order Component)

React의 기술 중 하나로, 컴포넌트 로직을 재사용하기 위한 패턴
  • 컴포넌트를 가져와서 새로운 컴포넌트를 반환하는 함수
  • 다른 컴포넌트에서 사용할 수 있는 독립적인 로직을 분리하고, 이를 필요한 곳에서 재사용할 수 있도록 하기 위함이다.
  • ex) 데이터 가져오기, 인증 등의 기능 컴포넌트 분리
  • HOC는 with 접두어를 붙인다.
  • 클래스형 컴포넌트에서 재사용 되는 로직을 공통으로 적용하거나, 독립적인 로직을 분리하는 경우에 사용되었고, 함수형 컴포넌트에서도 사용할 수 있다.
 

함수형 컴포넌트에서의 HOC 구현 예시

/* App.js */ function App() { return <div> <ShowImg /> </div> } /* withLoading.js */ export default function withLoading(Element, url) { return (props) => { const [data, setData] = useState(null); useEffect(() => { fetch(url) .then(res => res.json()) .then(data => setData(data)); }, []); if(!data) return <div>Loading...</div> return <Element {...props} data={data} /> } } /* ShowImg.js */ function ShowImg(props) { return ( <div>{props.data}</div> ); } export default withLoading(ShowImg, "http://localhost/")
  • 위의 코드에서는 widhLoading이라는 HOC를 구현하여 컴포넌트와 url을 props로 전달받아 데이터를 fetch 해오기 전까지 Loading이 나타나도록 하는 공통된 로직을 처리한다.
  • 함수형 컴포넌트에서 HOC를 구현할 때는 return이 함수로 묶여서 처리되어야 한다.
 

Composing

HOC를 조합하는 기술
  • 여러 HOC를 조합할 수 있다.
  • ex) withToast(withLoader(withHover(Element))
  • 하지만 해당 조합은 자칫 Wrapper Hell을 야기할 수 있다.
    • Wrapper Hell: 코드를 감싸는 형태가 지속적으로 늘어나면서 복잡성을 유발하는 상태
 

HOC 장점

  1. 코드 재사용
      • 동일한 코드를 다양한 위치에서 사용하는 경우에 HOC로 구성하여 코드를 간결화할 수 있다.
  1. 렌더링 여부 제어
      • HOC 내의 컴포넌트에 요소를 추가하거나 조건에 따라 렌더링되지 않도록 처리할 수 있다.
  1. 상태 추상화
      • 여러 컴포넌트가 특정 상태를 공유하고 조작해야 하지만, 상위 컴포넌트로 상태를 올리는 것을 원하지 않을 때 사용할 수 있다.
  1. props 조작
      • HOC 내의 컴포넌트로 제공되는 props를 읽고
  1. 컴포넌트 라이프사이클 메소드 접근
      • HOC 내의 컴포넌트가 마운트 되기 전, 언마운트 될 때 특정 작업을 수행하도록 처리 할 수 있다.

HOC 단점

  1. 암묵적인 props 전달
      • HOC 패턴으로 코드를 개발하면 전달되는 props가 어떤 값인지 명확히 알기 어렵다.
      • HOC 패턴 내에서 추가로 전달하는 props 뿐만 아니라 ShowImg 내에서 사용하는 props의 종류까지 코드를 보고 모두 알고 있어야 한다.
  1. 컴포넌트 구조의 복잡성 유발(Wrapper Hell)
      • 다양한 HOC가 존재할 때, 특정 컴포넌트가 여러 개의 HOC를 필요로 한다면 코드에 depth가 깊어지며 복잡성이 높아진다.
      • withLoading(withCheck(withClear(Element))); 와 같은 형태로 코드가 개발될 수 있다.
      • depth가 깊어지면 디버깅 또한 어려워진다.
      • 복잡성을 해결하기 위해 하나의 HOC로 통합하는 라이브러리를 사용할 수 있지만, 이는 라이브러리에 의존하게 될 수 있다.
  1. props 네이밍 중복
      • 전달받은 컴포넌트 내에서 사용하는 props 이름과 HOC 패턴에서 직접 붙여주는 props의 이름이 겹칠 수 없다.
      • 전달받은 컴포넌트 내의 props 명칭을 모두 파악한 상태에서 네이밍을 해줘야 하기 때문에 불편함을 야기한다.
  1. TypeScript에서의 타입 정의
      • TypeScript를 사용하는 환경에서 HOC는 컴포넌트에 props를 수정하거나 추가하는 경우에 대한 타입 지정을 해야한다.
      • 새로운 props가 추가되는 경우에 대한 타입 지정을 해야하기 때문에 주의가 필요하다.
 

HOC vs Hooks

클래스형 컴포넌트 시절 React에서는 HOC를 통해서 로직 분할 및 재사용성 향상을 구현했으나, 함수형 컴포넌트가 등장하며 Hooks가 도입되었다.
Hooks는 클래스형 컴포넌트의 생명주기 메소드를 대체함과 더불어 재사용성이 높은 로직을 HOC보다 더 간단하게 구현할 수 있어 현재는 대부분의 코드에서 Hooks를 통해 로직을 분리한다.
하지만, 아직 HOC가 구현할 수 있는 모든 기능을 Hooks로 대체할 수 없기 때문에 HOC를 사용하는 경우가 있다.
또한, 클래스형 컴포넌트로 코드를 구현하는 경우에는 Hooks를 사용할 수 없기 때문에 HOC를 사용해야한다.
 

출처