CS/프로그래밍

고차 컴포넌트 (HOC, High Order Component)

고래강이 2024. 1. 20. 16:10

고차 컴포넌트

하나의 개발 패턴으로 컴포넌트를 인자로 받아서 새로운 컴포넌트로 변환해 반환하는 방식으로 인자로 넘긴 컴포넌트에게 추가되길 원하는 로직을 HOC에서 가지고 있다가 로직이 적용된 엘리멘트를 반환하게 된어 단 관심사 문제를 해결하는 역할을 한다.

 

 

횡단 관심사란?

 

대표적인 예시로 인증 & 인가 서비스, 로깅, 트랙젝션 처리, 에러처리 등등이 있으며 계층 분리를 통해서 기능을 분리한다고 하여도 중복된 코드가 생길 수 밖에 없는 경우가 있다 이러한 계층에 상관없이 공통적으로 필요한 관심사가 있는데 이것이 횡단 관심사이다.

즉 어플리케이션 전반에 공통적으로 필요한 문제를 횐단 관심사라고 부를 수 있다.

<App>
  <Router>
    <Container>
      <Page>
      
// 이렇게 관심사 분리를 통해서 분리를 해도 공통적으로 필요한 로직이 있는 경우가 있다

 

 

언제 사용하는가?

횡단 관심사 문제를 해결하는데 사용되는데 쉽게 말하자면 동일한 작업을 여러번 반복하기 싫을 때 모듈화를 통해서 비슷한 유형을 다른 인자를 전달하므로 원하는 값을 출력할 수 있는데 HOC의 경우도 이러한 경우 사용된다.

 

데이터 패칭을 통해서 리스트를 보여주는 2개의 컴포넌트가 있다.

export default UserList(){
	const [userList, setUserList] = useState([]); 
  
  	useEffect(() => {
      fetch('http://userList.plz.com/users')
      	.then(response => response.json())
      	.then(data => setUserList(data));
    }, []);
  
  	return userList.length < 1 ? <Loading /> : (
    	<h2>UserList</h2>
    	{
    		userList.map(user => (
    			<div key={user.id}>
    				<p>name: {user.name}</p>
  					<p>email: {user.email}</p>
                </div>
    		))
		}
    	
    );
}
export default PostList(){
	const [postList, setPostList] = useState([]); 
  
  	useEffect(() => {
      fetch('http://postList.plz.com/posts')
      	.then(response => response.json())
      	.then(data => setPostList(data));
    }, []);
  
  	return postList.length < 1 ? <Loading /> : (
    	<h2>postList</h2>
    	{
    		postList.map(post => (
    			<div key={user.id}>
                  <p>{post.body}</p>
                </div>
    		))
		}
    	
    );
}
  • 위 코드는 사실 불러온 데이터 값과 map()에 의해 돌아가는(표현되는) 아이템의 형태가 다를 뿐 나머지는 거의 동일한 형태의 컴포넌트이다.
  • 이러한 경우 동일하게 수행되는 부분을 HOC를 통해서 표현하고 나머지 부분(표현되는 것)에 집중할 수 있다.

아래는 HOC를 통해서 새롭게 수정된 함수이다.

// withLoading.js
export default withLoading(WrappedComponent) {
	return function({dataSource, ...otherProps}) {
     	const [data, setData] = useState([]);
      
      	useEffect(() => {
          fetch(dataSource)
          	.then(response => response.json())
          	.then(data => setData(data));
        }, []);
      
      	return data.length < 1 ? <Loading /> : <WrappedComponent data={data} {...otherProps} />
      	
    }
}
// UserList.jsx
const UserList = ({data}) => {
  return data.length < 1 ? <Loading /> : (
    	<h2>UserList</h2>
    	{
    		data.map(user => (
    			<div key={user.id}>
    				<p>name: {user.name}</p>
  					<p>email: {user.email}</p>
                </div>
    		))
		}
    	
    );
}

export default withLoading(UserList);

이렇듯 우리는 HOC를 통해 동일 로직을 처리하며 간단하게 컴포넌트를 생성할 수 있다.

 

장 · 단점

장점

  • 앞선 내용과 같이 한 곳에서 구현한 로직을 여러 컴포넌트에서 재사용할 수 있다.
  • 동일 구현을 통해 버그 발생률을 줄일 수 있다.
  • 관심사의 분리를 통해 가독성과 집중해서 구현할 수 있다.

 

단점

  • props를 통해서 전달을 하게 되면서 중복되는 부분이 있고 이러한 부분에 대해서 props 병합을 통해서 결하게 될 시 코드에 대한 가독성이 나빠져 유지 보수 측면에서 방해가 된다.
function withStyles(Component) {
  return props => {
    const style = {
      padding: '0.2rem',
      margin: '1rem',
      ...props.style
    }

    return <Component style={style} {...props} />
  }
}

const Button = () = <button style={{ color: 'red' }}>Click me!</button>
const StyledButton = withStyles(Button)

style에 대한 병합을  통해서 나타냄으로 파악하기 힘들어진 구조...

 

 

주의사항

  • 항상 함수로 감싸줘야 한다.
  • render 메소드 안에서 HOC를 사용하면 안된다.
  • 정적 메소드는 따로 복사해야 한다.
  • ref는 전달되지 않는다. (React.forwardRef API 사용)

 

 

 

 

 

참고자료

'CS > 프로그래밍' 카테고리의 다른 글

[CS Study] 디자인 패턴  (0) 2024.02.04
솔리드 원칙  (1) 2024.01.20
CSS vs CSS-in-CSS vs CSS-in-JS  (1) 2024.01.13
CSS in JS  (1) 2024.01.13
화살표 함수와 일반 함수  (0) 2024.01.08