하이오더컴포넌트는 리액트 진영에서 기반한 컴포넌트 개발 패턴이다. 컴포넌트의 로직을 훼손하지 않고 재사용성을 최대로 끌어올리는 것에 장점이 있지만, 중간 컴포넌트가 하나 추가되므로 depth가 깊어질 수록 컴포넌트 구조가 복잡해지는 단점이 존재한다.
반대로 믹스인의 경우 HOC에 비해 쉽고 고차 컴포넌트에 대한 생각을 안해도 되지만, SOC(관심사 분리), 컴포넌트 테스트 측면에서 HOC에 비해 불리하다.
때문에 둘의 패턴을 적절히 활용해 반복되는 코드의 역할을 단일화시키며 재활용이 용이하게, 외부 종속성을 최소화 하는 것을 유의하며 적용해야 한다.
믹스인(Mixins)
🎄글로벌 믹스인(Global Mixins)
믹스인은 여러 Vue 컴포넌트에서 기능을 재사용하는 방법이다. 컴포넌트가 믹스인을 사용하면 모든 옵션과 기능이 컴포넌트 자체 옵션에 "혼합"되거나 "병합"된다. 글로벌 믹스인은 mixin 속성을 사용하여 Vue 인스턴스에서 사용되며 사용하는 모든 컴포넌트에서 옵션을 사용할 수 있다.
new Vue({
el: '#demo',
mixins: [MyMixin]
});
예를들어 만약 매우 간단한 Translate 서비스를 구현하려고 한다면 모든 컴포넌트가 번역 문자열을 조회할 수 있도록 번역 기능을 제공해야 한다.
new Vue({
el: '#demo',
mixins: [Translate],
data: {
locale: "en"
}
});
Translate 믹스인은 다음과 같다.
// some example translations of two languages
const TRANSLATIONS = {
en: {
firstName: "Firstname",
age: "Age"
},
de: {
firstName: "Vorname",
age: "Alter"
}
};
const Translate = Vue.mixin({
methods: {
translate(key) {
return TRANSLATIONS[this.$root.locale][key];
}
},
ready() {
// default locale set to "en"
this.$root.$set("locale", "en");
}
});
Translate 기능은 Vue 인스턴스와 키로 구성된 로케일을 사용하여 번역된 문자열을 찾는다. Vue 인스턴스가 로케일을 설정하지 않는 경우 준비된 LifeCycle Hook를 사용하여 직접 설정한다. Translate 믹스인을 Vue 인스턴스와 혼합하여 이제 다른 컴포넌트에서 번역(key) 함수를 호출할 수 있다.
이제 이름과 나이를 렌더링하는 작은 카드 컴포넌트를 작성하겠다.
Vue.component("card-profile", {
template: "#card-profile-template",
props: {
firstName: String,
age: String
}
});
<template id="card-profile-template">
<div>
<h2>My Profile</h2>
<div>
:
</div>
<div>
:
</div>
</div>
</template>
컴포넌트를 정의했고 이제 이를 렌더링 할 수 있을 것이다.
<div id="demo">
<card-profile first-name="Michael" age="30" />
<card-profile first-name="Lana" age="32" />
</div>
글로벌 믹스인의 모든 옵션은 모든 컴포넌트에 혼합되어 빠르게 사용할 수 없게 되므로 왠만하면 사용하지 않아야 한다. 이런 경우 로컬 믹스인을 사용할 수 있다.
🎄로컬믹스인(Local Mixins)
로컬 믹스인의 옵션은 해당 믹스인을 사용하는 컴포넌트요소에만 적용되며 Global에 비해서 더 쉽게 관리할 수 있다.
const DataLoader = Vue.mixin({
data() {
return {
loading: false,
response: null
}
},
methods: {
load(url) {
this.loading = true;
return axios.get(url)
.then(response => {
this.response = response.data;
this.loading = false;
})
}
}
});
axios를 다시 사용하여 원격 URL에서 일부 데이터를 가져오는 로드 방법을 제공하며 로드 및 응답 데이터가 제공된다.
이제 컴포넌트에서 이 믹스인을 사용해 보겠다.
Vue.component("article-card", {
mixins: [DataLoader],
template: "#article-card-template",
created() {
this.load("https://jsonplaceholder.typicode.com/posts/1")
}
});
믹스인 옵션을 사용하여 DataLoader를 사용하고 생성된 라이프사이클 Hooks에서 믹스인이 제공하는 로드 기능을 호출한다. article-card 컴포넌트의 템플릿은 로드 상태에 따라 데이터를 표시한다.
<template id="article-card-template">
<div>
<span v-if="loading">Loading...</span>
<div v-else>
<h2></h2>
</div>
</div>
</template>
실제로 컴포넌트에 자체 상태 로드 또는 응답이 있는 경우 믹스인 상태와 충돌하고 확실히 혼동을 일으킬 것이다. 메서드, 컴포넌트 및 지시문 옵션도 마찬가지다. 컴포넌트에는 항상 우선 순위가 있고 LifeCycle 메서드에 관해서는 둘 다 호출되지만 mixin의 메서드가 먼저 호출된다.
컴포넌트 확장(Extending Components)
컴포넌트 확장으로 Vue.js에서 코드를 재사용할 수도 있다. 바로 extends를 활용하는 것이다. extends를 활용해 원래 컴포넌트의 대부분의 코드를 재사용하는 새 컴포넌트를 빌드해 보도록 하겠다.
const BaseArticleCard = Vue.component("base-article-card", {
props: ["id"],
template: "#base-article-card-template",
data() {
return {
loading: false,
title: "",
body: "",
userId: ""
}
},
computed: {
articleTitle() {
return `Article: ${this.title}`;
}
},
methods: {
load(id) {
this.loading = true;
return axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`)
.then(response => {
this.title = response.data.title;
this.body = response.data.body;
this.userId = response.data.userId;
this.loading = false;
});
}
},
created() {
this.load(this.id);
}
});
로드 메서드를 통해 생성된 hooks에서 axios 요청을 통해 데이터를 가져오기 위해 유사한 코드를 재사용했다.
<template id="base-article-card-template">
<div class="article-card">
<div v-if="loading">Loading...</div>
<div v-else>
<h2>Vue.js Mixins, extending components and High Order Components</h2>
</div>
</div>
</template>
만약 이같은 형식을 반복한다고 했을 때 상속처럼 간단히 확장할 수 있다.
Vue.component("advanced-article-card", {
extends: BaseArticleCard,
template: "#advanced-article-card-template"
});
그리고 템플릿에 적용해 보겠다.
<template id="advanced-article-card-template">
<div class="article-card">
<div v-if="loading">Loading...</div>
<div v-else>
<h2></h2>
<p>Written by User ID: </p>
</div>
</div>
</template>
개인적으로 확장을 사용하는 것은 믹스인과 유사해보이며, 믹스인이 더 합리적이라는 생각이 든다. 상속을 받아 컴포넌트를 재정의할 필요없이 좀 더 직관적으로 믹스인을 받아 재정의하는 것이 합당한 것 같다.
하이 오더 컴포넌트(HOC)
같은 방식으로 데이터 로딩 컴포넌트를 구성해 보겠다. 데이터를 가져오고 props를 통해 데이터를 컴포넌트와 함께 전달하는 HOC로 이전 예제의 카드 컴포넌트를 확장해보겠다. Vue-cli를 통해 SFC 환경에서 구성해보겠다.
// our component
import ArticleCard from "./components/ArticleCard.vue";
// the HOC function
import withLoader from "./withLoader";
// our combined resulting component
const ArticleCardWithLoader = withLoader(ArticleCard);
ArticleCard 컴포넌트를 입력으로 가져오고 확장 컴포넌트 ArticleCardWithLoader를 반환한다. 보통 위와 같이 사용하거나 라우터에서 코드스플리팅으로 적용해 사용해보았는데 확실히 컴포넌트 depth가 깊어져 복잡해지는 문제점이 있었다. 컴포넌트가 아닌 js로 작성합니다. React의 구성과 유사하다.
// withLoader.js
import Vue from "vue";
import axios from "axios";
const withLoader = component => {
return Vue.component("with-loader", {
render(createElement) {
return createElement(component, {
props: {
loading: this.loading,
title: this.title,
body: this.body
}
});
},
props: ["id"],
data() {
return {
loading: false,
title: "",
body: "",
}
},
methods: {
load(id) {
this.loading = true;
return axios.get(`https://jsonplaceholder.typicode.com/posts/${id}`)
.then(response => {
this.title = response.data.title;
this.body = response.data.body;
this.loading = false;
});
}
},
created() {
this.load(this.id);
}
});
};
export default withLoader;
상위와 하위 컴포넌트 사이에 HOC가 위치해 하위 컴포넌트를 래핑하는 모양을 가지고 있다. 렌더 함수의 로딩, 제목 및 본문등을 전달하는 새 컴포넌트를 반환한다. 그리고 반환된 컴포넌트 자체만으로는 HOC 컴포넌트에 의해 래핑된다는 것을 알 수 없다.
<template>
<div class="article-card">
<div v-if="loading">Loading...</div>
<div v-else>
<h2>Vue.js Mixins, extending components and High Order Components</h2>
</div>
</div>
</template>
<script>
export default {
props: {
loading: Boolean,
title: String,
body: String
}
};
</script>
이는 props를 상위에서 하위로 전달하기 위해서는 HOC 컴포넌트를 변경하지 않고는 추가 props를 전달할 수 없다. 하지만 덜 의존적이고 SOC 측면에서 믹스인보다 유리하다.
결론
Vue.js 공식 레퍼런스에서는 HOC 대신 믹스인과 scoped-slot을 통한 구성을 선호하고 있다. 하지만 아직까지 지속적으로 토론 중인 주제이고 개인적으로도 개인의 성향과 사용여부에 따라 크게 상관없이 사용하면 된다고 생각한다. 개인적으로 다음 프로젝트에 앞서 기존 앱을 리팩토링하면서 지속적으로 사용 중인데, 후에 사용 후기를 남길 예정이다.
출처: https://fdietz.de/posts/vue.js-mixins-extending-components-and-high-order-components/
'Web > Vue' 카테고리의 다른 글
Vue 점진적 도입(2): SFC 전환 경험 공유 (0) | 2021.12.06 |
---|---|
Vue.js로 점진적 도입 경험 공유 (0) | 2021.07.19 |