HOC

박상욱·2022년 3월 10일
0

React

목록 보기
14/20

HOC(higher order component)

고차 컴포넌트는 컴포넌트를 가져와서 새 컴포넌트를 반환하는 함수이다.
인자를 컴포넌트로 받고 return으로 새로운 컴포넌트를 해준다.


이점

코드의 재사용

일반적인 component와 HOC의 차이

컴포넌트는 props로 ui를 반환하고 고차 컴포넌트는 컴포넌트를 새로운 컴포넌트로 반환한다.


사용법

compoenent의 기능을 더한 새로운 컴포넌트를 리턴

const EnhancedComponent = higherOrderComponent(wrappedComponent);

사용시기

동일한 패턴이 반복적으로 발생해서 이 로직을 한곳에 정의하고 많은 컴포넌트에서 로직을 공유할 수 있게하는 추상화가 필요할 때 고차 컴포넌트를 사용하자.

  • 공통된 기능들을 구현하고 그것을 재사용하게 한다.
  • redux의 connect와 유사하다.

사용되는 함수

withSubscription
구독한 데이터를 prop으로 전달 받는 자식 컴포넌트를 파라미터 중 하나로 받는 함수.

const DataListWithSubscription = withSubscription(DataList, (DataSource) => DataSource.getDatas())

첫번째 인자 : 어떤 component가 그려질지(wrapping될 component)
두번째 인자 : 그 데이터를 어떻게 가져올것인가에 대한 함수

withSubscription 는 일반 함수이기때문에 원하는 갯수의 인수를 추가할 수 있습니다. 예를 들어 래핑된 컴포넌트로부터 고차 컴포넌트를 더 격리시키기 위해 data prop 이름을 설정 가능하게 만들 수 있습니다. 혹은 shouldComponentUpdate 설정을 위한 인수를 받게 하거나 데이터 소스를 설정하는 인수를 받게할 수도 있습니다. 고차 컴포넌트가 컴포넌트 정의 방법을 완전히 제어할 수 있기 때문에 이러한 작업들이 모두 가능합니다.


고차 컴포넌트의 일반적인 사용

const ConnectionComment = connect(commentSelector, commentActions)(CommentList)
// connect는 다른 함수를 반환하는 함수이다.
const enhance = connect(commentSelector, commentActions)
// 반환하는 함수는 Redux store에 연결된 컴포넌트를 반환하는 고차 함수 컴포넌트이다.
const ConnectedComment = enhance(CommentList);

예제

예제는 input,button을 가지고 있는 컴포넌트가 공통적으로 1초 후에 화면을 보여주는 예제이다.
이렇듯 공통관심사를 묶어 HOC로 만든다.

withLoading.js

export default function withLoading(Component) {
    //withLoading은 함수이기때문에 hooks를 사용하지 못한다.
    //그래서 Componet를 반환해준다.
    const WithLoadingComponent = (props) => {
        
        console.log('props', props);
        const [loading, setLoading] =  useState(true);
        
        useEffect(()=> {
        const timer = setTimeout(()=>setLoading(false),1000);
    
        return () => clearTimeout(timer);
        },[]);

      return loading ? <p>Loading...</p> : <Component {...props}/>;
    }
    return WithLoadingComponent;

}

Button.jsx

import React,{useEffect, useState} from 'react'
import withLoading from './withLoading'

function Button() {
  return <button>Button</button>;
}

export default withLoading(Button);

Button.jsx

import React,{useEffect, useState} from 'react'
import withLoading from './withLoading'

function Input() {
  return (
    <input type={'text'}></input>
  )
}

export default withLoading(Input);

고차함수의 디버깅

간단한 디버깅을 위한 Display name 작성
다른 구성요소와 마찬가지로 HOC로 만든 컨테이너 구성요소도 dev tools에 표시는된다.
이때 디버깅을 쉽게하려면 HOC의 결과임을 알리는 이름을 작성해야한다.
가장 일반적인 방법 ->HOC 내부 컴포넌트이름으로 감싸는것.

WithSubscription.displayName = 'WithSubscription(${getDisplayName(WrappedComponent)})'

주의점

  1. 원본 컴포넌트를 직접 변경하지마라!
  • HOC는 컴포넌트를 받아서 내려줄때 사이에 어떤 동작들을 넣어주는거지 컴포넌트의 didMount Update등 프로토타입을 수정하면 안된다.
    이유: 입력된 컴포넌트를 확장된(enhandced)된 컴포넌트와 별도로 재사용 할수 없기 때문이다.
    componentDidUpdate를 변형하는 확장 컴포넌트에 또다른 Hoc를 적용하면 첫번재 HOC의 기능은 무시된다.
  1. HOC는 생명주기 메서드가 없는 함수 컴포넌트에서 작동하지 않는다.
  • HOC는 누출된 추상화(leaky abstraction)이다. HOC와의 충돌을 피하기 위해 어떻게 구현되어 있는지 알아야한다.
  1. HOC는 변경(mutation ) 대신에 입력 컴포넌트를 컨테이너 구성요소로 감싸서 조합(composition)을 사용해야한다.
  2. render 메서드 안에서는 HOC를 사용하면 안된다.
  • 랜더링 최적화를 할때 react가 요소의 주솟값으로 변경점을 체크할탠데, 계속 새로운 컴포넌트가 재생성되면서 전체 tree가 대체된다.
    (즉 요소의 주솟값이 바뀌기때문에 새로 그려지기때문에 사용하면 안된다.)
  1. ref는 전달되지 않는다.
  • 고차 컴포넌트는 모든 props를 래핑된 컴포넌트에 전달하는게 원칙이지만,
    ref는 실제 prop가 아닌 key처럼 특별하게 취급한다.
    컴포넌트가 HOC의 결과인 엘리먼트에 ref를 추가하는 경우, ref는 래핑된 컴포넌트가 아닌 가장 바깥쪽 컴포넌트의 인스턴스를 나타내기때문이다.
    (이문제를 해결하기위해선 React.forwardRef를 사용하자. (react 16.3))

결론

전달받은 componet는 어떤 변경을 가하지말고 그대로 내려줘야한다는 뜻이다.

profile
개발자

0개의 댓글