공부 및 일상기록

[React] HOC에 대해 설명 본문

개발/React

[React] HOC에 대해 설명

낚시하고싶어요 2023. 1. 17. 03:52

고차컴포넌트 (HOC, Higher order component)는 컴포넌트 로직을 재사용하기 위한 React의 고급 기술이다. 고차 컴포넌트는 React API의 일부가 아니며, React의 구성적 특성에서 나오는 패턴이다.

 

고차컴포넌트는 컴포넌트를 가져와 새 컴포넌트를 반환하는 함수이다.

 

주의사항

  • render메서드 안에서 고차컴포넌트를 사용하면 안된다.
    • 컴포넌트의 정의 바깥에 HOC를 적용하여 컴포넌트가 한번만 생성되어야 한다.
  • 정적 메서드는 반드시 따로 복사해야 한다.
    • 메서드를 반환하기 전에 컨테이너에 복사한다.
    • hoist-non-react-statics를 사용하여 모든 non-React 정적 메서드를 자동으로 복사한다.
    • 정적 메서드를 컴포넌트와 별도로 내보낸다.
  • ref는 전달되지 않는다.
    • React.forwardRef API를 사용한다.

 

 

 

사용방법

HOC는 with로 시작하는 컨벤션으로 파일을 작성한다.

먼저 예제로 작성할 코드를 보자

 

1. App.js가 다음과 같이 존재한다.

import Input from './components/Input'
import Button from './components/Button'
 
function App() {
  return (
    <>
      <Input /><br />
      <Button />
    </>
  );
}

 

 

2. Button컴포넌트는 다음과 같다.

import { useState, useEffect } from 'react';
 
// Loading 3초 후 button이 보이는 Button 컴포넌트
export default function Button() { 
  const [loading, setLoading] = useState(true);
 
  useEffect(() => {
    const timer = setTimeout(() => setLoading(false), 3000);
    return () => clearTimeout(timer);
  }, []);
 
  return loading ? <span>Loading...</span> : <button>Button</button>;
}

 

3. Input컴포넌트는 다음과 같다.

import { useState, useEffect } from 'react';
 
// Loading 3초 후 input이 보이는 Input 컴포넌트
export default function Input() { 
  const [loading, setLoading] = useState(true);
 
  useEffect(() => {
    const timer = setTimeout(() => setLoading(false), 3000);
    return () => clearTimeout(timer);
  }, []);
 
  return loading ? <span>Loading...</span> : <input defaultValue="Input" />;
}

 

자세히 읽다보면 button과 input은 동일한 역할을 수행하는 부분이 있다.

바로 loading 시간을 3초 기다리고 그동안은 loading을 보여주는 것이다.

 

따라서 아래와 같이 withLoading 이라는 컴포넌트로 묶어 낼 수 있다.

import { useState, useEffect } from 'react';
 
export default function withLoading(Component) {
  const WithLoadingComponent = (props) => {
    const [loading, setLoading] = useState(true);
 
    useEffect(() => {
      const timer = setTimeout(() => setLoading(false), 3000);
      return () => clearTimeout(timer);
    }, []);
 
    return loading ? <span>Loading...</span> : <Component {...props} />;
  };
 
  return WithLoadingComponent;

그럼 이제 묶어내었으므로 버튼과 인풋의 코드도 바뀌어야 한다.

import withLoading from './withLoading';
 
function Button() {
  return <button>Button</button>;
}
 
export default withLoading(Button);
import withLoading from './withLoading';
 
function Input() {
  return <input defaultValue="Input" />;
}
 
export default withLoading(Input);

위처럼 중복된 코드를 제거해내고 export default에 withLoading(해당컴포넌트) 를 넣어서 동작하면 동작이 잘 된다.