Published on

react - 고차 컴포넌트

Authors

출처

고차 컴포넌트

고차 컴포넌트는 다른 컴포넌트를 props 로 받고, 추가적인 로직이나 스타일을 적용한 컴포넌트를 반환하는 디자인 패턴이다.

동일한 스타일링을 다수의 컴포넌트에 적용을 시키는 상황을 떠올려보자. style 객체를 지역마다 매번 생성하는 것 대신에, style 객체를 더해주는 고차 컴포넌트를 활용할 수 있다.

function withStyles(Component) {
  return props => {
    const style = { padding: '0.2rem', margin: '1rem' }
    return <Component style={style} {...props} />
  }
}

const Button = () = <button>Click me!</button>
const Text = () => <p>Hello World!</p>

const StyledButton = withStyles(Button)
const StyledText = withStyles(Text)
  • withStyles 컴포넌트에 원하는 공용 컴포넌트를 넘기면, 스타일이 입혀진 컴포넌트가 반환된다.

예시 : images fetch 후에 동일 스타일 적용하기

데이타 fetch 요청할 때, 이미지가 로드되기 전까지는 '로딩중' 임을 유저에게 나타내고 싶을 때가 있다. 데이터 요청이 완료 전까지 로딩중에 해당하는 컴포넌트 (스피너, 메시지) 를 화면에 그릴 때에도 HOC 패턴을 적용해볼 수 있다.

function withLoader(Element) {
  return (props) => <Element />
}
  • 아직 요소 자체만을 반환하고 있는, 기본적인 형태의 껍데기 컴포넌트를 만들었다.
// widthLoader.js
import React, { useEffect, useState } from 'react'
export default function withLoader(Element, url) {
  return (props) => {
    const [data, setData] = useState(null)
    useEffect(() => {
      async function getData() {
        const res = await fetch(url)
        const data = await res.json()
        setData(data)
      }
      getData()
    }, [])
    if (!data) {
      return <div>Loading...</div>
    }
    return <Element {...props} data={data} />
  }
}
  • 강아지 이미지 뿐 아니라, 더 범용적으로 사용할 수 있게 url 을 props 로 내려 받는다.
  • 데이타 요청이 성공하기 전까지 'Loading...' 이라는 메시지가 화면에 보이도록 한다.
// DogImages.js
import React from 'react'
import withLoader from './withLoader'

function DogImages(props) {
  return props.data.message.map((dog, index) => <img src={dog} alt="Dog" key={index} />)
}

export default withLoader(DogImages, 'https://dog.ceo/api/breed/labrador/images/random/6')
  • 이제 DogImages.js 는 withLoader 고차 컴포넌트로 감싼 DogImages 를 반환한다.
  • 그 결과 각 이미지 별로 데이타 요청을 하고 실패할 시에 Loading 중이라는 텍스트를 화면에 그린다. 이처럼 고차 컴포넌트를 이용하면, 동일한 로직을 다수의 컴포넌트에 적용할 수 있다. withLoader 는 어떤 컴포넌트, url 이 오든지에 상관없이 대응할 수 있다.

예시: 고차 컴포넌트 합성하기

// withHover.js
import React, { useState } from 'react'

export default function withHover(Element) {
  return (props) => {
    const [hovering, setHover] = useState(false)
    return (
      <Element
        {...props}
        hovering={hovering}
        onMouseEnter={() => setHover(true)}
        onMouseLeave={() => setHover(false)}
      />
    )
  }
}
  • hover 시에 특정 이벤트를 발생하는 고차 컴포넌트를 추가했다.
// DogImages.js
export default withHover(
  withLoader(DogImages, 'https://dog.ceo/api/breed/labrador/images/random/6')
)
  • 이제 DogImages 컴포넌트는 widthLoader 와 withHover 고차 컴포넌트로 감싸진 컴포넌트를 반환한다.

고차 컴포넌트의 장단점

장점:

  • 코드 재사용성 향상
  • 로직 분리
  • 유연성 증가
  • 상태 관리 간소화

단점

export default withAuth(withRouter(withStyles(SomeComponent)))
<withAuth>
  <withLayout>
    <withLogging>
      <Component />
    </withLogging>
  </withLayout>
</withAuth>
  • 복잡한 컴포넌트의 중첩 상태
  • 디버깅 시 어려움
  • Props 전달 문제
  • 성능 이슈
  • 리액트 개발자 도구에서의 가독성 저하

Hook 패턴과 비교

// useHover.js
import { useState, useRef, useEffect } from 'react'

export default function useHover() {
  const [hovering, setHover] = useState(false)
  const ref = useRef(null)
  const handleMouseOver = () => setHover(true)
  const handleMouseOut = () => setHover(false)

  useEffect(() => {
    const node = ref.current
    if (node) {
      node.addEventListener('mouseover', handleMouseOver)
      node.addEventListener('mouseout', handleMouseOut)

      return () => {
        node.removeEventListener('mouseover', handleMouseOver)
        node.removeEventListener('mouseout', handleMouseOut)
      }
    }
  }, [ref.current])

  return [ref, hovering]
}
// DogImages.jsx
function DogImages(props) {
  const [hoverRef, hovering] = useHover();
  • withHover 컴포넌트를 useHover 훅으로 대체 가능
  • 이제 컴포넌트가 다른 컴포넌트를 복잡하게 래핑하는 구조가 발생하지 않음
  • 함수형 컴포넌트가 인기를 가진 뒤에, 많은 개발자들이 hook 패턴을 HOC 보다 선호하게 됨
특성고차 컴포넌트(HOC)훅(Hooks)
사용 방식컴포넌트를 감싸서 새로운 컴포넌트를 반환함수형 컴포넌트 내에서 상태와 로직 사용
코드 재사용성고차 컴포넌트를 통해 로직을 재사용커스텀 훅을 통해 로직을 재사용
문법 복잡성중첩된 HOC로 인해 복잡해질 수 있음함수형 컴포넌트 내에서 간결하게 작성 가능
디버깅 용이성중첩된 HOC로 인해 디버깅이 어려울 수 있음상태와 로직이 컴포넌트 내부에 있어 디버깅 용이
성능중첩된 HOC로 인해 성능 이슈가 발생할 수 있음간결한 문법으로 성능 최적화 가능
생명주기 메서드클래스형 컴포넌트에서 주로 사용함수형 컴포넌트에서 사용 가능

**HOC의 모범 사용 사례:

  • 애플리케이션 전체에서 많은 컴포넌트가 커스텀 필요가 없는 동일한 동작을 사용해야 할 때,
  • 컴포넌트가 커스텀되지 않고 독립적으로 작동할 수 있다. Hooks의 모범 사용 사례:
  • 동작을 사용하는 각 컴포넌트에 대해 동작을 커스텀해야 할 때
  • 동작이 애플리케이션 전체에 퍼지지 않고 하나 또는 몇 개의 컴포넌트만 동작을 사용할 때
  • 동작이 컴포넌트에 많은 속성을 추가할 때

결론

고차 컴포넌트는 여전히 유용한 패턴이지만, 훅의 등장으로 인해 많은 경우 훅이 HOC를 대체하게 되었다. 특히, 상태 관리나 생명주기 메서드 사용이 필요한 경우 훅이 더 간편하고 직관적이다. 하지만 HOC는 특정 상황에서 여전히 유용할 수 있으며, 훅과 HOC를 함께 사용하여 두 패턴의 장점을 모두 활용할 수도 있다.

따라서, HOC가 완전히 사라진 것은 아니며, 적절한 상황에서 여전히 사용될 수 있음을 기억해두면 좋다. 다만, 최신 리액트 프로젝트에서는 훅을 사용하는 것이 일반적인 트렌드긴 하다.