💡 Mixin은 여러 컴포넌트 간에 공통으로 사용되고 있는 로직, 기능들을 재사용하는 방법 중 하나이다. Mixin에 정의 할 수 있는 재사용 로직은 data, methods, created 등과 같은 컴포넌트의 옵션이다.
위의 말대로 Mixin은 뷰 컴포넌트에서 사용되는 공통되는 로직들을 하나로 캡슐화 시켜 여러 곳에서 사용 할 수 있게 해주는 유용한 방법이다.
Mixin의 문법은 아래와 같다.
let HelloMixins = {
// 컴포넌트 옵션들 (data, methods, created 등...)
};
new Vue({
mixins: [HelloMixins],
});
먼저 Mixin을 활용하기 위해 Vue v2.x.x 버젼을 활용했다.
먼저 Mixin을 만든다. (Mixin의 작명 규칙은 "동사+able"의 형용사 형태로 작명한다.)
firstMixin.js
let firstMixin = {
data() {
return {
data: 10,
}
},
created() {
console.log("firstMixin");
},
methods: {
onClick() {
console.log("click");
this.data += 1;
}
}
}
export default firstMixin;
secondMixin.js
let secondMixin = {
props: {
message: {
default: "Bye",
},
},
created() {
console.log("secondMixin");
},
methods: {
onClick() {
this.data += 5;
}
},
};
export default secondMixin;
thirdMixin.js
let thirdMixin = {
props: {
message: {
default: "Hello",
},
},
created() {
console.log("thirdMixin");
},
methods: {
onClick() {
this.data += 10;
}
},
};
export default thirdMixin;
믹스인들이 완성이 되었다면 vue 컴포넌트에 적용하면 완료
<template>
<div id="app">
<div id="sub" @click="onClick" @contextmenu.prevent="onRightclick">
{{ data }} {{ message }}
</div>
</div>
</template>
<script>
// 1. 작성한 믹스인을 import
import firstMixin from "@/components/mixin/firstMixin";
import secondMixin from "@/components/mixin/secondMixin";
import thirdMixin from "@/components/mixin/thirdMixin";
export default {
name: "App",
mixins: [firstMixin, thirdMixin, secondMixin] // 2. mixin을 적용할 땐 배열에 적용
data() {
return {
data: 5,
}
},
methods: {
onRightClick() {
this.data--;
}
},
};
</script>
이렇게 작성하고 로컬 서버를 실행시켜 페이지를 확인한다면 아래와 같은 화면이 출력된다.
화면상으로 봤을 때 출력되는 부분은 {{ data }} {{ message }}
이지만 data는 App.vue
에서 보유한 데이터를 렌더링하는 중이고 Bye라는 글자는 App.vue
에서 보유하지 않고 secondMixin
에서 보유한 props
의 default message
를 렌더링 하고 있다.
또한, 기존에 Mixin들을 만들어 둘 때 created
훅에 각 믹스인의 이름을 콘솔에 출력하도록 해놨는데 순차대로 호출되는 것이 아니고 thirdMixin과 secondMixin이 순서가 바뀌어서 호출되었다.
위 처럼 Mixin과 각 컴포넌트에서 보유한 구성 요소 자체에 겹치는 옵션들이 포함되어 있으면 병합이 이루어지는데 Mixin이 import되는 순서가 기준이 아닌 컴포넌트에서 Mixin배열에 적용되는 순서가 기준으로 가장 마지막에 적용되는 Mixin이 우선권을 갖게 된다.
⛔️ But, 최상위 우선권은 Mixin에서 보유한 옵션들이 아닌 컴포넌트에서 보유한 옵션들이 최우선권을 갖고 적용된다.
위의 상황을 예로 들자면
data
가 적용되는 부분message
가 적용되는 부분methods
가 적용되는 부분Mixin기능은 컴포넌트 내의 반복되는 로직을 줄여주는데 매우 효율적인 방법 중 하나이다.
하지만 프로젝트 규모가 작을 땐 Mixin을 활용하는데 효과적으로 다가오지만 그 규모가 점점 더 커지게 된다면 "병합"이라는 개념으로 인해 추후 사용중인 Mixin의 옵션들이 어디서 받아오는 Mixin 로직들인지 추적하기가 어렵게 되고 옵션들의 위주로 재사용이 되는 방법이기 때문에 여전히 컴포넌트의 재사용을 줄이는데에는 한계가 있으며 컴포넌트가 옵션 최우선권을 갖는다는 점에 있어 Mixin을 잘못 활용하게 된다면 오히려 유지보수하는데 있어 더 큰 어려움을 겪을 것이다.
이를 활용하기 위해서는 데이터의 구조 단계에 있어 설계가 매우 중요하게 받아들여진다.
💡 고차 컴포넌트(HOC, high-order component)는 컴포넌트 로직을 재사용하기 위한 React의 고급 기술입니다. 고차 컴포넌트는 그 자체로는 React API의 일부분이 아닙니다. 고차 컴포넌트는 React의 컴포넌트적 성격에서 나타나는 패턴입니다. (React.js docs)
고차 컴포넌트는 고차 함수의 컴포넌트 버젼으로 느껴졌다.
고차 함수의 함수가 함수를 리턴해주는 개념을 바탕으로 고차 컴포넌트에서는 컴포넌트가 컴포넌트를 새롭게 리턴해주는 개념으로 생각하면 될 것 같다.
예시로 댓글들을 fetch하여 렌더링 해주는 Comment라는 컴포넌트와 게시글들을 fetch하여 렌더링 하는 Post컴포넌트가 존재한다.
<!-- Comment.vue -->
<template>
<div>{{ data }}</div>
</template>
<script>
import axios from 'axios';
export default {
name: "Comment",
data() {
return {
data: null,
}
},
async created() {
const res = await axios.get("https://jsonplaceholder.typicode.com/comments?postId=1");
this.data = res.data;
},
</script>
<!-- Post.vue -->
<template>
<div>{{ data }}</div>
</template>
<script>
import axios from 'axios';
export default {
name: "Post",
data() {
return {
data: null,
}
},
async created() {
const res = await axios.get("https://jsonplaceholder.typicode.com/posts/1");
this.data = res.data;
},
};
</script>
해당 컴포넌트에서 보면 데이터를 fetch하는 로직(axios함수)이 중복되고 있다. 이를 HOC로 변환하려면
먼저 js파일을 제작해 함수를 만든다.
HOC를 제작할 때 작명은 통상적으로 "With"라는 접두어를 붙여 제작한다.
/* WithRequest.js */
import Vue from 'vue';
import axios from 'axios';
const WithRequest = (url) => (component) => {
return Vue.component("WithRequest", {
data() {
return {
fetchedData: null,
};
},
async created() {
const res = await.get(url);
this.fetchedData = res.data;
},
render(createElement) {
return createElement(component, {
props: {
data: this.fetchedData,
},
});
},
});
};
export { WithRequest };
활용하고자 하는 컴포넌트에서 해당 HOC를 import해서 활용한다.
<!-- App.vue -->
<template>
<post-page />
<comment-page />
</template>
<script>
import Post from '@/components/Post.vue';
import Comment from '@/components/Comment.vue';
import { WithRequest } from '@/WithRequest';
const postUrl = "https://jsonplaceholder.typicode.com/posts/1";
const commentUrl = "https://jsonplaceholder.typicode.com/comments?postId=1";
export default {
name: "App",
components: {
// WithRequest에서 url을 받고 component를 받아 새로운 component를 리턴하는 구조이다.
"post-page": WithRequest(postUrl)(Post),
"comment-page": WithRequest(commentUrl)(Comment),
},
};
</script>
컴포넌트를 import할 때 WithRequest함수를 같이 import하고 해당 컴포넌트를 주입하여 준다.
상위 컴포넌트에서 셋팅이 완료되었다면 해당 컴포넌트를 수정해준다.
<!-- Comment.vue -->
<template>
<div>{{ data }}</div>
</template>
<script>
export default {
name: "Comment",
props: {
data: Array,
},
};
</script>
<!-- Post.vue -->
<template>
<div>{{ data }}</div>
</template>
<script>
export default {
name: "Post",
props: {
data: Object,
},
};
</script>
Mixin은 컴포넌트 내의 옵션들의 공통부분을 간결하게 작성할 수 있다면 HOC는 컴포넌트 자체의 로직을 간결하게 만들 수 있다는게 큰 장점이라고 생각한다.
Mixin은 Vue의 템플릿을 다루지 않고 옵션만을 재사용 가능하게 만들어 주지만 HOC는 컴포넌트 그 자체를 재사용 함으로써 템플릿까지 포함해 캡슐화가 가능한 부분이다.
Mixin과의 용법에 있어 확연한 차이는 있지만 궁극적으로는 반복되는 코드의 양을 줄이기 위한 방법 중 하나로 둘 의 장단점은 존재한다.
하지만 Vue.js에서의 공식 레퍼런스에선 HOC보단 Mixin과 scoped-slot을 통한 구성을 선호한다고 한다. 이는 선호의 방식일 뿐이지 이렇게 해라 라는 정답은 아니기 때문에 상관없이 스타일에 맞춰 활용하면 될 것 같다.
장점은 두 가지 기술 모두 컴포넌트의 코드가 간결해지면서 코드의 재활용성이 높아진다는 점이다.
Vue v3.0부터는 기존의 HOC와 Mixin의 단점을 극복하고자 Composition API를 도입하게 되었다. 하지만 Vue 2에서도 Composition API를 활용할 수 있도록 plugin을 제공한다. → vuejs/composition-api
💡 Composition API는 인스턴스의 옵션 단위가 아니라 특정 기능이나 논리의 단위로 코드를 그룹화하고 그 그룹화된 로직을 여러 컴포넌트에서 재사용하는 것이 가고자 하는 방향이다.
Mixin과 HOC모두 반복되는 코드를 줄여준다는 점에 있어 매우 유용한 방법이었으나 위에서 말한듯이 프로젝트의 규모가 커질 수록 활용하는데 어려움을 겪을 수 있으며 유지보수가 까다로워 진다는 점이 아쉬운 점 중 하나이다.
Composition API는 데이터 그룹핑에 있어 매우 용이하고 데이터 흐름을 파악하고 유지보수가 매우 편해진다는 큰 장점이 있다.
또한 함수를 재사용 하는데 있어서도 아주 유용하다는 점에 있어 반복되는 코드를 import해서 api내부에서 사용함으로써 유틸함수 재사용에 매우 좋다.
Composition API는 꼭 Mixin과 HOC를 대체하기 위해 등장한 API는 아니다 공식문서에서도 Composition API가 등장했으니 기존에 활용하던 Option API는 없어지지 않을 것이라고 명시했고 Option API에서도 마찬가지로 Composition API를 활용할 수 있다. (setup함수 활용)
또한 Composition API가 함수형 프로그래밍은 아니라고 확실히 정의를 해놨고 Option API와 Composition API 둘 중 꼭 무엇을 사용해라! 라는 것도 아니고 개인의 코딩 스타일 취향에 맞춰서 사용하면 된다고 설명하고 있다.
하지만 Composition API를 활용하는데 있어 강력한 장점은 아래와 같다.
setup()
함수 내에서 그룹화 되면서 정리가 될 수 있고 여기에 더해 <script setup>
을 명시해줌으로써 setup함수를 사용했을 때 return까지 명시하지 않아도 되는 편리함이 있다.
- 네임스페이스가 충돌할 가능성이 높습니다. 예를 들어 두 믹스인을 함께 사용한다면, 서로 같은 이름의 메소드가 존재할 수 있습니다. 또한, 믹스인을 사용하는 해당 컴포넌트에서도 충돌한 이름으로 메소드를 정의할 수 없습니다. 이를 회피하려면 사전에 엄격하게 컨벤션을 합의해야 하는데, 번거롭고 쉽지 않은 일입니다. 컴포저블이었다면 다른 컴포저블과 충돌하는 경우 이름을 변경할 수 있으므로 이러한 문제로부터 자유롭습니다.
- 파라미터를 전달할 수 없습니다. 만약 여러 믹스인이 서로 상호작용하는 관계라면, 어떤 공유되는 값에 의존해야 하므로 결합도가 높아집니다. 컴포저블은 일반 함수처럼 다른 컴포저블에 파라미터를 전달할 수 있습니다.
- 여러 믹스인을 동시에 사용할 때, 제각기 어떤 믹스인에 의해 주입된 것인지가 명확하지 않아 레퍼런스를 추적하거나 동작을 이해하기가 난해합니다.