ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [React] 고차 컴포넌트 (HOC, High Order Component)
    프로그래밍/React 2024. 1. 20. 16:09

     

    고차 컴포넌트

    하나의 개발 패턴으로 컴포넌트를 인자로 받아서 새로운 컴포넌트로 변환해 반환하는 방식으로 인자로 넘긴 컴포넌트에게 추가되길 원하는 로직을 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 사용)

     

     

     

     

     

    참고자료

    댓글

Designed by Tistory.