고차 컴포넌트는 컴포넌트를 매개변수로 받고 새 컴포넌트를 반환하는 함수이다
Redux의 connect와 Relay의 createFragmentContainer와 같은 서드 파티 React 라이브러리에서 흔하게 볼 수 있는 패턴이다.
=> 매개변수로 받은 컴포넌트는 불변성이 유지되어야 한다.
고차 컴포넌트는 원본 컴포넌트를 컨테이너 컴포넌트로 포장(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만 분리한다
아래와 같은 패턴은 고차 컴포넌트의 유연성과 재사용성을 보장합니다
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 메서드 안에서 고차 컴포넌트를 사용할 수 없습니다
컴포넌트의 정의 바깥에 HOC를 적용해서 컴포넌트가 한번만 생성되도록 해야 합니다. 그러면 여러번 렌더링이 되더라도 바뀌지 않습니다.
컴포넌트에 HOC가 적용되면, 새 컴포넌트에선 기존 컴포넌트의 정적메소드를 가지고 있지 않습니다.
이 문제를 해결하려면 메서드를 반환하기 전에 컨테이너에 복사합니다.
function enhance(WrappedComponent) {
class Enhance extends React.Component {/*...*/}
// 복사 할 메서드를 정확히 알아야 합니다.
Enhance.staticMethod = WrappedComponent.staticMethod;
return Enhance;
}
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';
React.ref
로 컴포넌트를 전달하면 작동하지 않기 때문에, React.forwarRef
를 사용해야 합니다.