고차 컴포넌트 (HOC : High Order Component)는 "컴포넌트 로직을 재사용하기 위한 React의 고급 기술"이라고 React 공식 문서에 작성되어 있습니다.
고차 컴포넌트는 컴포넌트를 인자로 받아 새로운 컴포넌트를 리턴하는 함수입니다.
const EnhancedComponent = higherOrderComponent(WrappedComponent);
이러한 방식으로 고차 컴포넌트에 인자로 전달되는 모든 컴포넌트는 고차 컴포넌트에 정의된 함수를 사용할 수 있는 새 컴포넌트로 변화하여 재사용성을 높일 수 있습니다.
이러한 고차 컴포넌트(HOC)는 Redux의 connect와 Relay의 createFragmentContainer와 같은 서드 파티 React 라이브러리에서 흔하게 볼 수 있습니다.
고차 컴포넌트는 어떻게 사용할 수 있을까? 이해를 돕기 위해 React 예제를 사용하겠습니다.
아래는 외부로부터 데이터를 구독하여 댓글 목록을 렌더링하는 CommentList 컴포넌트입니다.
class CommentList extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
// "DataSource" 는 글로벌 데이터 소스입니다.
comments: DataSource.getComments()
};
}
componentDidMount() {
// 변화감지를 위해 리스너를 추가합니다.
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
// 리스너를 제거합니다.
DataSource.removeChangeListener(this.handleChange);
}
handleChange() {
// 데이터 소스가 변경될때 마다 comments를 업데이트합니다.
this.setState({
comments: DataSource.getComments()
});
}
render() {
return (
<div>
{this.state.comments.map((comment) => (
<Comment comment={comment} key={comment.id} />
))}
</div>
);
}
}
다음은 그리고 블로그 포스트를 구독하기 위해 위와 비슷한 패턴으로 컴포넌트입니다.
class BlogPost extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
blogPost: DataSource.getBlogPost(props.id)
};
}
componentDidMount() {
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
DataSource.removeChangeListener(this.handleChange);
}
handleChange() {
this.setState({
blogPost: DataSource.getBlogPost(this.props.id)
});
}
render() {
return <TextBlock text={this.state.blogPost} />;
}
}
두 컴포넌트를 확인해보면 상당한 유사성을 확인할 수 있습니다. handleChange, state, listener를 공통적으로 가지고 있습니다.
우리는 공통되는 부분 확인하고 해당 부분을 재사용하면 좋겠다라는 생각이 들게 될 것 입니다.
그렇다면 아래와 같이 수정해 보겠습니다.
// 이 함수는 컴포넌트를 매개변수로 받고..
function withSubscription(WrappedComponent, selectData) {
// ...다른 컴포넌트를 반환하는데...
return class extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
data: selectData(DataSource, props)
};
}
componentDidMount() {
// ... 구독을 담당하고...
DataSource.addChangeListener(this.handleChange);
}
componentWillUnmount() {
DataSource.removeChangeListener(this.handleChange);
}
handleChange() {
this.setState({
data: selectData(DataSource, this.props)
});
}
render() {
// ... 래핑된 컴포넌트를 새로운 데이터로 랜더링 합니다!
// 컴포넌트에 추가로 props를 내려주는 것에 주목하세요.
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
}
이와 같은 과정을 통해 우리는 코드의 재사용성을 높일 수 있게 되었습니다.
Reference : React Docs - High Order Component