- Vue 필터
- 이벤트 허브
- <template functional>
slot
속성의 이전 구문- Props 기본 함수
this
접근 -
@vue/compat
와 호환되지 않는 라이브러리 처리
Vue 3로의 이주
Vue 2에서 3으로의 이주는 epic &6252에서 추적됩니다.
Vue 3.x로의 이주를 용이하게 하기 위해, 코드베이스에서 사용 중인 다음과 같은 폐기된 기능을 사용하지 못하게 하는 ESLint rules를 추가했습니다.
Vue 필터
왜?
Vue 3 API에서 필터는 완전히 제거되었습니다.
대신 사용할 것
컴포넌트의 계산된 속성 / 메소드 또는 외부 도우미.
이벤트 허브
왜?
Vue 3에서는 Vue 인스턴스에서$on
, $once
, $off
메서드가 제거되었으므로 이제 이벤트 허브를 만들 수 없습니다.
언제 사용할 것인가
이벤트 허브를 사용하지 않는 Vue 앱을 사용 중이라면 절대적으로 필요하지 않은 한 새로운 이벤트 허브를 추가하지 않도록 노력하세요. 예를 들어, 자식 컴포넌트가 부모의 이벤트에 반응해야 하는 경우 부모로부터 prop을 전달하는 것이 좋습니다. 그런 다음, 자식 컴포넌트에서 해당 prop에 대한 watch 속성을 사용하여 원하는 부작용을 만들어냅니다.
다른 컴포넌트 간 통신이 필요하다면(다른 Vue 앱 간), 그럴 때는 새 허브를 추가하는 것이 옳은 결정일 수 있습니다.
대신 사용할 것
새로 mitt와 유사한 이벤트 허브를 인스턴스화하는 데 사용할 수 있는 팩토리를 만들었습니다.
기존 이벤트 허브를 새로운 권장된 방식으로 이주하거나 새 이벤트 허브를 만드는 것을 쉽게 만들어줍니다.
import createEventHub from '~/helpers/event_hub_factory';
export default createEventHub();
팩토리로 생성된 이벤트 허브는 이전의 Vue 2 이벤트 허브와 같은 메서드($on
, $once
, $off
및 $emit
)를 노출하여 이전 방식과 호환되도록 만들었습니다.
<template functional>
왜?
Vue 3에서{ functional: true }
옵션이 제거되었으며<template functional>
은 더 이상 지원되지 않습니다.
대신 사용할 것
함수형 컴포넌트는 평범한 함수로 작성해야 합니다.
import { h } from 'vue'
const FunctionalComp = (props, slots) => {
return h('div', `안녕하세요! ${props.name}`)
}
성능 향상이 반드시 필요한 경우가 아니라면 상태 유지형 컴포넌트를 함수형 컴포넌트로 대체하는 것은 권장되지 않습니다. Vue 3에서 함수형 컴포넌트의 성능 향상은 미미합니다.
slot
속성의 이전 구문
왜?
Vue 2.6에서 slot
속성은 v-slot
지시문을 선호하여 폐기되었습니다. slot
속성 사용은 여전히 허용되며 때로는 단순한 유닛 테스트를 위해 사용하는 경우가 있습니다(shallowMount
에서 슬롯이 렌더링됩니다). 그러나 Vue 3에서는 이전 구문을 더 이상 사용할 수 없습니다.
대신 사용할 것
v-slot
지시문 구문. shallowMount
에서 슬롯을 렌더링하기 위해 자식 컴포넌트를 명시적으로 스텁해야 합니다.
<!-- MyAwesomeComponent.vue -->
<script>
import SomeChildComponent from './some_child_component.vue'
export default {
components: {
SomeChildComponent
}
}
</script>
<template>
<div>
<h1>Hello GitLab!</h1>
<some-child-component>
<template #header>
Header content
</template>
</some-child-component>
</div>
</template>
// MyAwesomeComponent.spec.js
import SomeChildComponent from '~/some_child_component.vue'
shallowMount(MyAwesomeComponent, {
stubs: {
SomeChildComponent
}
})
Props 기본 함수 this
접근
왜?
Vue 3에서는 prop 기본 값 공장 함수가 더 이상 this
(컴포넌트 인스턴스)에 접근할 수 없습니다.
대신 사용할 것
다른 prop에서 원하는 값을 해결하는 계산된 prop을 작성하십시오. 이는 Vue 2 및 3 모두에서 작동합니다.
<script>
export default {
props: {
metric: {
type: String,
required: true,
},
title: {
type: String,
required: false,
default: null,
},
},
computed: {
actualTitle() {
return this.title ?? this.metric;
},
},
}
</script>
<template>
<div>{{ actualTitle }}</div>
</template>
Vue 3에서, props 기본 값 공장은 원시 props을 인수로 받으며 인젝션에도 액세스할 수 있습니다.
@vue/compat
와 호환되지 않는 라이브러리 처리
문제
일부 라이브러리는 Vue.js 2 내부에 의존합니다. 이러한 라이브러리는 @vue/compat
과 함께 작동하지 않을 수 있으므로 현재 코드베이스와 호환성을 유지하면서 Vue.js 3의 업데이트된 버전을 사용하기 위한 전략이 필요합니다.
목표
- 새 라이브러리를 지원하기 위해 기존 코드에 가능한 적은 변경을 추가해야 합니다. 대신 새로운 코드를 추가하고 이전 버전과 호환되도록 작동할 것으로 하는 퍼사드 역할을 하는 코드를 추가해야 합니다.
- 새 버전과 이전 버전 간의 전환은 도구(웹팩 / 제스트) 내부에서만 처리되어야 하며 코드에 노출되지 않아야 합니다.
- 이주 관련 특정 퍼사드는 모두 동일한 디렉터리에 있어야 하여 향후 이주 단계를 간소화해야 합니다.
단계별 이주
이 단계별 가이드에서는 VueApollo 데모 프로젝트를 이주할 것입니다. 이를 통해 복잡한 도구 설정의 세부 사항을 회피하면서 이주 구체적인 사항에 중점을 둘 수 있게 됩니다. 프로젝트는 의도적으로 같은 도구를 사용합니다:
- 웹팩
- yarn
- Vue.js + VueApollo
초기 상태
클론한 직후에 yarn serve
를 사용하여 Vue.js 2로 VueApollo 데모를 실행할 수 있으며, yarn serve:vue3
을 사용하여 Vue.js 3(compat
빌드)로 실행할 수 있습니다. 그러나 후자는 즉시 다음과 같은 오류가 발생합니다.
Uncaught TypeError: Cannot read properties of undefined (reading 'loading')
VueApollo v3(사용된 Vue.js 2용)이 Vue.js compat
에서 초기화에 실패합니다.
Vue.version
을 스텁하면 VueApollo 관련 문제를 해결할 수 있지만 여전히 특정 시나리오에서 반응이 상실될 수 있으므로 업그레이드가 필요합니다.단계 1. 라이브러리 설명서에 따라 업그레이드 수행
VueApollo v4 설치 가이드에 따르면 @vue/apollo-option
를 설치해야 하며 응용 프로그램에 변경을 가해야 합니다.
--- a/src/index.js
+++ b/src/index.js
@@ -1,19 +1,17 @@
-import Vue from "vue";
-import VueApollo from "vue-apollo";
+import { createApp, h } from "vue";
+import { createApolloProvider } from "@vue/apollo-option";
import Demo from "./components/Demo.vue";
import createDefaultClient from "./lib/graphql";
-Vue.use(VueApollo);
-
-const apolloProvider = new VueApollo({
+const apolloProvider = createApolloProvider({
defaultClient: createDefaultClient(),
});
-new Vue({
- el: "#app",
- apolloProvider,
- render(h) {
+const app = createApp({
+ render() {
return h(Demo);
},
});
+app.use(apolloProvider);
+app.mount("#app");
이 변경 사항은 데모 프로젝트의 01-upgrade-vue-apollo 브랜치에서 확인할 수 있습니다.
단계 2. Vue.js 2 및 3에서 애플리케이션을 확장하는 방법의 차이 해결
Vue.js 2에서 VueApollo
와 같은 도구는 “느긋하게” 초기화됩니다:
// 우리는 VueApollo "핸들러"를 나중에 어떤 데이터를 처리할지 담당하도록 등록하고 있습니다
Vue.use(VueApollo)
// ...
// apolloProvider가 앱 생성 시 제공되며,
// 이전에 등록된 VueApollo가 이를 처리하게 됩니다
new Vue({ /- ... */, apolloProvider })
Vue.js 3에서는 두 단계가 하나로 Merge되었습니다. 즉, 즉시 핸들러를 등록하고 구성을 전달합니다:
app.use(apolloProvider)
이러한 동작을 되감아야 할 경우 다음 지식이 필요합니다:
- Vue 인스턴스로 제공된 추가 옵션에는
$options
을 통해 액세스할 수 있으므로 추가apolloProvider
는this.$options.apolloProvider
로 볼 수 있습니다. - Vue.js 3에서 현재 ‘app’에는
this.$.appContext.app
을 통해 Vue 인스턴스에서 액세스할 수 있습니다.
@vue/compat
빌드가 3.2.x 브랜치에서만 사용할 수 있을 것으로 기대되므로 이 API가 변경될 위험을 줄였습니다.이러한 지식을 바탕으로 Vue2에서 우리의 도구 초기화를 가능한 한 빨리 이동할 수 있습니다. 이를 위해 beforeCreate()
라이프사이클 훅을 사용합니다:
--- a/src/index.js
+++ b/src/index.js
@@ -1,4 +1,4 @@
-import { createApp, h } from "vue";
+import Vue from "vue";
import { createApolloProvider } from "@vue/apollo-option";
import Demo from "./components/Demo.vue";
@@ -8,10 +8,13 @@ const apolloProvider = createApolloProvider({
defaultClient: createDefaultClient(),
});
-const app = createApp({
- render() {
+new Vue({
+ el: "#app",
+ apolloProvider,
+ render(h) {
return h(Demo);
},
+ beforeCreate() {
+ this.$.appContext.app.use(this.$options.apolloProvider);
+ },
});
-app.use(apolloProvider);
-app.mount("#app");
이 변경 사항은 데모 프로젝트의 02-bring-back-new-vue 브랜치에서 확인할 수 있습니다.
단계 3. VueApollo
클래스 재생성
Vue.js 3 라이브러리(및 Vue.js 자체)는 이전의 new Vue
대신 createApp
과 같은 팩토리를 사용하는 것을 선호합니다.
VueApollo
클래스는 두 가지 목적으로 사용되었습니다:
-
apolloProvider
생성을 위한 생성자 - 컴포넌트에서 apollo 관련 로직을 설치
우리는 코드베이스에 존재했던 Vue.use(VueApollo)
코드를 활용하여 여기에 mixin을 숨기고 앱 코드 수정을 피할 수 있습니다:
--- a/src/index.js
+++ b/src/index.js
@@ -4,7 +4,26 @@ import { createApolloProvider } from "@vue/apollo-option";
import Demo from "./components/Demo.vue";
import createDefaultClient from "./lib/graphql";
-const apolloProvider = createApolloProvider({
+class VueApollo {
+ constructor(...args) {
+ return createApolloProvider(...args);
+ }
+
+ // Vue.use에 의해 호출됨
+ static install() {
+ Vue.mixin({
+ beforeCreate() {
+ if (this.$options.apolloProvider) {
+ this.$.appContext.app.use(this.$options.apolloProvider);
+ }
+ },
+ });
+ }
+}
+
+Vue.use(VueApollo);
+
+const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
@@ -14,7 +33,4 @@ new Vue({
render(h) {
return h(Demo);
},
- beforeCreate() {
- this.$.appContext.app.use(this.$options.apolloProvider);
- },
});
이 변경 사항은 데모 프로젝트의 03-recreate-vue-apollo 브랜치에서 확인할 수 있습니다.
단계 4. VueApollo
클래스를 별도의 파일로 이동하고 별칭 설정
이제 우리는 Vue.js 2 버전과 거의 동일한 코드(임포트를 제외하고)를 가지고 있습니다.
우리는 패러드를 별도의 파일로 이동하고 webpack
이 vue-apollo
가 import된 경우에만 조건적으로 실행하도록 설정할 것입니다:
--- a/src/index.js
+++ b/src/index.js
@@ -1,5 +1,5 @@
import Vue from "vue";
-import { createApolloProvider } from "@vue/apollo-option";
+import VueApollo from "vue-apollo";
import Demo from "./components/Demo.vue";
import createDefaultClient from "./lib/graphql";
diff --git a/webpack.config.js b/webpack.config.js
index 6160d3f..b8b955f 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -12,6 +12,7 @@ if (USE_VUE3) {
VUE3_ALIASES = {
vue: "@vue/compat",
+ "vue-apollo": path.resolve("src/vue3compat/vue-apollo"),
};
}
(VueApollo
클래스를 index.js
에서 vue3compat/vue-apollo.js
로 옮기고 기본 내보내기는 명확성을 위해 생략되었습니다)
이 변경 사항은 데모 프로젝트의 04-add-webpack-alias 브랜치에서 확인할 수 있습니다.
단계 5. 결과 확인
이 시점에서 yarn serve
를 사용하여 모두 Vue.js 2 버전 및 yarn serve:vue3
를 사용하여 Vue.js 3 버전을 다시 실행할 수 있어야 합니다.
이전 단계의 모든 변경 사항이 표시되는 최종 MR은 (앱 코드인) index.js
에 대한 변경 사항이 없어야 했던 것이 목표였습니다
GitLab 프로젝트에서 이 접근 방식 적용
VueApollo v4 지원 추가 커밋에서 단계별 안내에서 다루지 않은 추가 세부 정보를 볼 수 있습니다:
- 패러드에 추가적인 import를 추가해야 할 수 있습니다(GitLab의 우리 코드는
ApolloMutation
컴포넌트를 사용합니다) - webpack 뿐만 아니라 jest에 대한 별칭도 업데이트해야 합니다. 이렇게 해야 테스트에서도 패러드를 소비할 수 있습니다.