💻 Frontend
리액트 고차 컴포넌트(HOC)
category
💻 Frontend
React 고차 컴포넌트
고차 컴포넌트(HOC, Higher Order Component)는 컴포넌트 로직을 재사용하기 위한 React의 고급 기술입니다. React API가 아니며, React의 구성적 특성에서 나오는 패턴입니다.
고차 컴포넌트란, 컴포넌트를 가져와 새 컴포넌트를 반환하는 함수입니다.
횡단 관심사 분리
고차 컴포넌트는 횡단 관심사를 분리하는데 사용한다.
잠깐, 횡단 관심사란?
횡단 관심사(cross-cutting concern) 여러 서비스에 걸쳐서 동작해야 하는 코드를 의미합니다. 추가로 애플리케이션 내 여러 핵심 비지니스 로직들에 걸쳐서 실행되어야 하는 동작들을 의미합니다.
횡단 관심사의 대표적인 예시를 보자면 아래와 같습니다.
<App> <Router> <Container> <Page>
- 전체 어플리케이션을 담당하는 App 컴포넌트 입니다.
- 브라우저 요청 따라 컴포넌트를 선택하는 것이 Router 컴포넌트 입니다.
- UI 렌더에 필요한 데이터 처리는 Container 컴포넌트의 역할입니다.
- UI를 렌더링하는 것은 Page 컴포넌트 입니다.
하지만 아무리 이렇게 계층으로 분리하여도 기능까지 완벽하게 분리할 수는 없습니다. 코드의 중복이 생길 수 밖에 없죠. 이처럼 각 계층을 넘어 공통으로 필요한 관심사가 있는데 이것을 횡단 관심사라고 합니다.
정리하면 어플리케이션 각 계층에서 공통적으로 필요한 문제를 횡단 관심사라고 부를 수 있습니다. 고차 컴포넌트는 이런 공통의 기능을 해결하는 역할을 합니다.
예제
- 컴포넌트가 렌더링 될 때마다 로그를 기록합니다.
- Button 컴포넌트를 클릭할 때마다 로그를 기록합니다.
import React, { useEffect } from 'react'; function Header() { useEffect(() => { // 로깅 console.log('[Header] 마운트'); }, []); return <header>Header</header>; }
import React, { useEffect } from 'react'; function Button() { useEffect(() => { // 로깅 console.log('[Button] 마운트'); }, []); const handleClick = () => { // 로깅 console.log('[Button] 클릭'); }; return <button onClick={handleClick}>클릭</button>; }
문제점
여기서 두가지 문제를 짚을 수 있습니다.
- 두 컴포넌트가 중복된 코드를 가지는 것입니다. 바로 컴포넌트가 렌더링 되었을 때 로그를 남기는 코드죠. 두 컴포넌트 모두 같은 생명주기 메서드를 사용하고 같은 로그 기록 함수를 호출합니다.
- 로깅이 각 컴포넌트 고유의 역할이 아니라는 것입니다. Header와 Button은 헤더 영역 UI를 렌더하고 클릭할 수 있는 버튼을 렌더하는 것만을 책임집니다. 결과물은 두 컴포넌트가 로깅을 해야하지만 코드를 구현할 때 로깅은 컴포넌트 안에 있어서는 안될 것 같다는 것입니다.
이러한 기능은 두 컴포넌트 어디에도 소속되어 있지 않습니다. 에플리케이션 전반에 사용되는 공통의 기능이죠.
이러한 문제를 코차 컴포넌트가 어떻게 해결할 수 있을까요?
해결 방법
- 컴포넌트를 래핑해서 프롭으로 전달
컴포넌트에 횡단 관심사를 추가할 수 있는 함수를 만듭니다. 고차 컴포넌트는 함수 이름이 with로 시작해야합니다.
import React, { useEffect } from 'react'; const withLogging = WrappedComponent => { function WithLogging(props) { useEffect(() => { log("마운트"); }, []); const enhancedProps = { log, }; return <WrappedComponent {...props} {...enhancedProps} />; } function log(message) { console.log(`[${getComponentName(WrappedComponent)}]`, message); } function getComponentName({ displayName, name }) { return displayName || name || "Component"; } return WithLogging; };
withLogging
은 컴포넌트를 받아 컴포넌를 반환하는 함수, 즉 고차 컴포넌트입니다.- 이 함수는 인자로 받은
WrappedComponent
를 내부에서 만든WithLogging
컴포넌트에서 사용합니다. 단순히 래핑한 정도로 보일지 모를테지만WithLogging
이 하는 일이 있습니다.
- 바로
enhancedProps
를 컴포넌트에 추가하는 것이죠.enhanceProps
는 컴포넌트의 역할과 무관한 범용적인 log 함수를 전달하는 역할을 합니다.
- 마지막으로 새로운 컴포넌트
WithLogging
을 반환합니다.
정리
- 컴포넌트를 인자로 받기
- Props를 생성하기
- 인자로 받은 컴포넌트에 생성한 props를 넘겨 새로운 컴포넌트 반환
withLogging
을 이용해 위의 예제를 다시 작성해 보죠.const Header = () => <header>Header</header> const Button ({log}) => { const handleClick = () => log("클릭"); return <button onClick={handleClick}>클릭</button>; } const EnhancedHeader = withLogging(Header) const EnhancedButton = withLogging(Button)
Header
와 Button
코드가 매우 단순하게 바뀌었습니다. 로깅 기능이 모두 withLogging
으로 빠졌기 때문이죠. 컴포넌트 본연의 기능만 남고 어플리케이션 공통의 기능을 고차 함수로 옮긴 셈입니다.마무리
면접 질문
Q: 고차 컴포넌트가 무엇인가요?
A: 컴포넌트를 받아 새로운 컴포넌트를 생성하는 패턴으로, 횡단관심사 문제를 해결하는 역할을 합니다.