HOC(Higher-Order Components) 패턴은 컴포넌트를 인자로 받아 새로운 컴포넌트를 반환하는 함수입니다. 즉, 컴포넌트 로직을 재사용하기 위한 패턴이며, React의 공식 패턴 중 하나입니다.
const EnhancedComponent = hiorderComponent(WrappedComponent);
고차 함수 방식을 컴포넌트로 변환한 디자인 패턴이라고 생각할 수 있습니다.
HOC는 주로 아래와 같은 목적으로 많이 사용합니다.
function WithLoading<P>(
WrappedComponent: React.ComponentType<P>
) {
return function WithLoading(props: P & {isLoading: boolean}) {
if(props.isLoading) {
return <div>Loading...</div>;
}
return <WrappedComponent {...props}/>;
}
}
위 코드는 클로저를 활용하여 WrappedComponent를 기억하고 isLoading을 통해 컴포넌트 렌더링을 분기처리하고 있습니다. 또한 위 코드는 함수에서 함수를 리턴하는 고차함수입니다.
const ApiWithLoading = WithLoading(ApiList);
<ApiWithLoading isLoading={true} />
하지만 현재 HOC는 거의 사용하지 않고 있습니다. 그 이유는 Hook의 등장이 큰 이유입니다. React 16.8 버전 이후 Hook이라는 재사용 로직을 통해 더욱 간편하게 모듈화를 할 수 있기 되었기 때문입니다. 또한 기존 Class형 컴포넌트에서 함수형 컴포넌트로 전환된 것도 큰 요인입니다.
import {defineComponent, h} from 'vue';
function withLoading(WrappedComponent){
return defineComponent({
props: {
isLoading: Boolean
},
setup(props, {attrs}) {
return () =>
props.isLoading
? h('div', 'loading...')
: h(WrappedComponent, attrs)
}
})
}
const ApiWithLoading = withLoading(ApiList);
이런 방식으로 <script setup> 방식이 아닌 defineComponent와 h메서드를 사용하면 HOC를 구현할 수 있습니다.
하지만 이러한 방식은 실무에서 거의 사용하지 않기 때문에 HOC 패턴은 Vue에서 거의 사용하지 않습니다.
또한 React는 컴포넌트 중심이지만 Vue는 Composition API와 같이 로직 중심적으로 이루어져 있기에 분리가 어렵다는 점이 있습니다.
그럼 Vue에서는 이러한 문제를 해결할 수 없을까요?
Vue에서는 HOC 패턴 대신 Composable과 Slot, Directive 방식을 사용하라고 권장하고 있습니다.
// useAuth.ts
export function useAuth() {
const isAuthenticated = ref(false);
const login = () => {
isAuthenticated.value = true;
};
return { isAuthenticated, login };
}
<script setup>
const { isAuthenticated } = useAuth();
</script>
<template>
<Login v-if="!isAuthenticated" />
<Dashboard v-else />
</template>
<AuthGuard v-slot="{isAuthenticated}">
<Dashboard v-if="isAuthenticated"/>
</AuthGuard>
//AuthGuard.vue
<script setup>
const isAuthenticated = true
</script>
<template>
<slot :isAuthenticated="isAuthenticated" />
</template>
export default {
mounted(el, binding) {
if(!binding.value){
el.remove();
}
}
}
import vAuth from '@/directives/vAuth';
app.directive('auth', vAuth);
<button v-auth="isAdmin">버튼</button>
현재 HOC는 거의 사용 안하는 추세이지만 알아두는것이 좋다고 생각합니다. 프로젝트를 진행하다보면 새로운 프로젝트를 만드는 것 뿐만이 아닌 레거시 코드를 마이그레이션하는 작업도 따라오기에 HOC 패턴을 적용했던 코드를 Hook으로 변환하거나 Composable로 변환할 수 있기 때문입니다. 오늘도 글 읽어주셔서 감사합니다:)