- Published on
react - 고차 컴포넌트
- Authors
- Name
- 정윤호
출처
고차 컴포넌트
고차 컴포넌트는 다른 컴포넌트를 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가 완전히 사라진 것은 아니며, 적절한 상황에서 여전히 사용될 수 있음을 기억해두면 좋다. 다만, 최신 리액트 프로젝트에서는 훅을 사용하는 것이 일반적인 트렌드긴 하다.