고차 컴포넌트

js·2022년 6월 28일
0

고차 컴포넌트는 컴포넌트를 매개변수로 받고 새 컴포넌트를 반환하는 함수이다

Redux의 connect와 Relay의 createFragmentContainer와 같은 서드 파티 React 라이브러리에서 흔하게 볼 수 있는 패턴이다.

횡단 관심사 (Cross-Cutting Concerns)

  • 고차 컴포넌트는 매개변수로 받은 원본 컴포넌트를 수정하지 않으며 상속을 사용하여 동작을 복사하지도 않습니다.

=> 매개변수로 받은 컴포넌트는 불변성이 유지되어야 한다.

  • 고차 컴포넌트는 원본 컴포넌트를 컨테이너 컴포넌트로 포장(Wrapping)하여 조합(compose)합니다.

  • 고차 컴포넌트는 사이드 이펙트가 전혀 없는 순수 함수입니다.

원본 컴포넌트를 변경하지 마세요. 조합하세요.

  • 고차 컴포넌트 내부에서 입력받은 컴포넌트의 프로토타입을 수정(또는 변경)하지 않도록 합니다.

  • 입력받은 컴포넌트를 생애주기 메소드로 변경하려하면 무시 됩니다

  • 입력받은 컴포넌트를 컨테이너 구성요소로 감싸서 조합(composition)을 사용해야 합니다.

아래와 같이 프로토타입을 변경하면 안됩니다.

function logProps(InputComponent) {
  InputComponent.prototype.componentDidUpdate = function(prevProps) {
    console.log('Current props: ', this.props);
    console.log('Previous props: ', prevProps);
  };
  // 원본의 입력을 반환한다는 것은 이미 변형되었다는 점을 시사합니다.
  return InputComponent;
}

// EnhancedComponent 는 props를 받을 때 마다 log를 남깁니다.
const EnhancedComponent = logProps(InputComponent);

조합하는 예시는 아래와 같습니다

function logProps(WrappedComponent) {
  return class extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('Current props: ', this.props);
      console.log('Previous props: ', prevProps);
    }
    render() {
      // 들어온 component를 변경하지 않는 container입니다. 좋아요!
      return <WrappedComponent {...this.props} />;
    }
  }
}

컨벤션: 래핑된 컴포넌트를 통해 관련없는 Props 전달하기

  • 현재 컴포넌트에서 사용되는 props만 분리한다

  • 아래와 같은 패턴은 고차 컴포넌트의 유연성과 재사용성을 보장합니다

render() {
  // 이 HOC에만 해당되므로 추가된 props는 걸러내어 이 HOC에 전달되지 않도록 합니다.
  const { extraProp, ...passThroughProps } = this.props;

  // 이 Props는 일반적으로 Status값 또는 Instance method 입니다.
  const injectedProp = someStateOrInstanceMethod;

  // wrapped component에 props를 전달합니다.
  return (
    <WrappedComponent
      injectedProp={injectedProp}
      {...passThroughProps}
    />
  );
}

컨벤션: 간단한 디버깅을 위한 디스플레이 네임 작성 방법

  • 예를들어, HOC의 이름이 withSubscription이고, HOC 내부의 컴포넌트의 이름이 CommentList 인 경우입니다.

  • 디스플레이 네임은 WithSubscription(CommentList)을 사용합니다.

function withSubscription(WrappedComponent) {
  class WithSubscription extends React.Component {/* ... */}
  WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;
  return WithSubscription;
}

function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}

주의사항

render 메서드 안에서 고차 컴포넌트를 사용하지 마세요

  • 애초에, render 메서드 안에서 고차 컴포넌트를 사용할 수 없습니다

  • 컴포넌트의 정의 바깥에 HOC를 적용해서 컴포넌트가 한번만 생성되도록 해야 합니다. 그러면 여러번 렌더링이 되더라도 바뀌지 않습니다.

정적 메서드는 반드시 따로 복사하세요

  • 컴포넌트에 HOC가 적용되면, 새 컴포넌트에선 기존 컴포넌트의 정적메소드를 가지고 있지 않습니다.

  • 이 문제를 해결하려면 메서드를 반환하기 전에 컨테이너에 복사합니다.

function enhance(WrappedComponent) {
  class Enhance extends React.Component {/*...*/}
  // 복사 할 메서드를 정확히 알아야 합니다.
  Enhance.staticMethod = WrappedComponent.staticMethod;
  return Enhance;
}
  • 이때 사용하는 라이브러리로, hoist-non-react-statics를 사용하여 모든 non-React 정적 메서드를 자동으로 복사합니다.
import hoistNonReactStatic from 'hoist-non-react-statics';
function enhance(WrappedComponent) {
  class Enhance extends React.Component {/*...*/}
  hoistNonReactStatic(Enhance, WrappedComponent);
  return Enhance;
}
  • 또 다른 해결 방법은 정적 메서드를 컴포넌트와 별도로 내보내는 것입니다.
// 대신에...
MyComponent.someFunction = someFunction;
export default MyComponent;

// ...메서드를 각각 내보내고...
export { someFunction };

// ...불러오는 모듈에서 두개를 다 임포트합니다.
import MyComponent, { someFunction } from './MyComponent.js';

ref는 전달되지 않는다

React.ref로 컴포넌트를 전달하면 작동하지 않기 때문에, React.forwarRef를 사용해야 합니다.

0개의 댓글