Higher-Order Components in React Hooks era
HOC는 컴포넌트를 인자로 받아서 다른 버전의 컴포넌트를 반환하는 컴포넌트 함수입니다.
tsx
// accept a Component as an argument
const withSomeLogic = (Component) => {
// do something
// return a component that renders the component from the argument
return (props) => <Component {...props} />;
};
hook이 알려지기 전에 HOC는 context에 접근하거나 외부 데이터 subscription을 구현할 때 자주 사용되었습니다.
hook만으로 구현할 수 없는 HOC만의 3가지 기능들을 정리하면 다음과 같습니다😎
Enhancing callbacks and React lifecycle events
컴포넌트는 원래 정의한대로 사용하되 플러그인을 설치하여 기능을 추가하는듯한 효과를 줄 수 있습니다.
tsx
type Base = { onClick: () => void };
// just a function that accepts Component as an argument
export const withLoggingOnClick = <TProps extends Base>(
Component: ComponentType<TProps>
) => {
return (props: TProps & { logText: string }) => {
const onClick = () => {
console.log(props.logText);
// don't forget to call onClick that is coming from props!
// we're overriding it below
props.onClick();
};
// return original component with all the props
// and overriding onClick with our own callback
return <Component {...props} onClick={onClick} />;
};
};
tsx
const ButtonWithLoggingOnClickWithProps = withLoggingOnClick(SimpleButton);
const Page = () => {
return (
<ButtonWithLoggingOnClickWithProps
onClick={onClickCallback}
logText="this is Page button"
>
Click me
</ButtonWithLoggingOnClickWithProps>
);
};
특정 컴포넌트가 mount되었을 때 수행할 로직도 HOC로 구현할 수 있습니다.
tsx
export const withLoggingOnMount = <TProps extends unknown>(
Component: ComponentType<TProps>
) => {
return (props: TProps) => {
// no more overriding onClick, just adding normal useEffect
useEffect(() => {
console.log("log on mount");
}, []);
// just passing props intact
return <Component {...props} />;
};
};
Intercepting DOM Events
동일한 DOM Event의 핸들링 로직을 여러 컴포넌트에서 사용하려는 경우, 위의 경우와 비슷하게 구현할 수 있습니다.
tsx
export const withSupressKeyPress = <TProps extends unknown>(
Component: ComponentType<TProps>
) => {
return (props: TProps) => {
const onKeyPress = (event) => {
event.stopPropagation();
};
return (
<div onKeyPress={onKeyPress}>
<Component {...props} />
</div>
);
};
};
tsx
const ModalWithSupressedKeyPress = withSupressKeyPress(Modal);
Context selctors
임의의 컴포넌트로 하여금 특정 context의 value만을 받아서 사용할 수 있는 context selector 컴포넌트를 구현할 수 있습니다.
tsx
export const withFormIdSelector = <TProps extends unknown>(
Component: ComponentType<TProps>
) => {
const MemoisedComponent = React.memo(Component) as ComponentType<
TProps & { formId: string }
>;
return (props: TProps) => {
const { id } = useFormContext();
return <MemoisedComponent {...props} formId={id} />;
};
};
위에서 MemoisedComponent는 context vaule 중 하나인 id만 변할 경우에만 리렌더링되도록 구현된 것을 알 수 있습니다.
jsx
// formId prop here is injected by the higher-order component below
const CountriesWithFormId = ({ formId }: { formId: string }) => {
console.log("Countries with selector re-render");
return (
<-- code is the same as before -->
);
};
jsx
const CountriesWithFormIdSelector = withFormIdSelector(CountriesWithFormId);
const Form = () => {
return (
<form css={pageCss}>
<Name />
<CountriesWithFormIdSelector />
</form>
);
};