원문: Albert Mulia Shintra, https://dev.to/chenxeed/be-prepared-to-migrate-your-vue-app-to-vue-3-eom
필자는 이 글에서 Vue 3 베타 버전을 사용한 경험, 특히 Vue 3로 마이그레이션할 때 주의해야 할 점을 얘기하고자 한다. 만약 이미 Vue 2로 개발된 앱을 Vue 3로 업그레이드할 예정이라면 이 글이 유용할 것이다.
아래의 목록은 성공적으로 Vue 3로 옮기는 데 많은 도움을 줄 것이며, Vue 2와는 달리 Vue 3에서는 문제를 일으키는 케이스를 피하는 데 도움을 줄 것이다.
$on
/ $once
/ $off
API를 사용하지 않는다.이벤트 버스는 자식에서 부모로, 또는 그 반대로 이벤트를 손쉽게 발생시키기 위해 사용하는 것으로, Vue로 개발할 때 흔히 들어본 용어일 것이다. 인터넷에 "vue event bus"를 검색해보면 이를 설명하는 수많은 글을 찾을 수 있을 것이다.
한 가지 알아둘 점은, 이것은 Vue가 추천하는 공식적인 방법이 아니다 🤯. 필자가 이를 말하는 이유는 Vue 공식 문서에서 이벤트 버스를 단 한 번도 언급하지 않았기 때문이다. 가장 관련 있는 문서는 "이벤트 허브(eventHub)"에 대한 내용이 있는 Vue 1.x에서 마이그레이션으로, Vuex를 쓰는 것을 추천한다.
이 패턴은 단순한 시나리오에서
$dispatch
와$broadcast
를 대체 할 수 있지만, 더 복잡한 경우에는 Vuex와 같은 전용 상태 관리 레이어를 사용하는 것이 좋다.
또한 RFC 문서에서 왜 Vue에서 이를 추천하지 않는지 확인할 수 있다.
이벤트 버스 개념이 프로그래밍에서 흔히 쓰이는 발행-구독(publish-subscribe) 패턴이기 때문에 아직은 mitt와 같은 다른 라이브러리를 이용하여 이 개념을 사용해도 무관하다. 😉
아래는 이벤트 버스의 리팩토링 예시이다.
// 이벤트 버스 예시 (Vue 2)
import Vue from 'vue';
const eventBus = new Vue();
// 구독
eventBus.$on('sandwich-made', () => console.log('sandwich made!'));
// 발행
eventBus.$emit('sandwich-made');
// 써드 파티 라이브러리(ex. mitt)를 사용하여 리팩토링
import mitt from 'mitt';
const eventBus = mitt();
// 구독
eventBus.on('sandwich-made', () => console.log('sandwich made!'));
// 발행
eventBus.emit('sandwich-made');
RFC 문서에 따르면 필터는 제거될 예정이라고 한다.
필터는 값을 특정 형식으로 바꿔주는 것을 도와준다. 예를 들어 대문자로 만든다거나, 통화 기호를 덧붙이거나, 짧은 URL로 만드는 것이다. 아마 필터는 Angular의 필터에 영향을 받았을 것으로 추측된다. 필터는 템플릿 문법 안에서 사용할 수 있기 때문에 가독성이 좋다. 아래는 숫자 값을 통화 형식으로 바꾸어주는 toCurrency
필터를 사용한 예시이다.
<div class="currency">{{ price | toCurrency }}</div>
price
가 25라면 toCurrency
필터가 적용된 결과는 $25.00이다.
필터는 사용하기 편리하지만 "구문 설탕(syntax sugar)"임을 명심해야 한다. Vue는 런타임에서 업데이트될 때마다 가격을 통화 형식으로 바꾸기 위해 항상 toCurrency
를 실행한다.
만약 toCurrency
필터를 method
로 변경한다면, 아래처럼 쓸 수 있다.
<div class="currency">{{ toCurrency(price) }}</div>
Vue 스크립트에서 리팩토링하는 방법은 단순히 함수를 filter
에서 method
로 옮기기만 하면 된다.
// 전: filter 사용
{
filter: {
toCurrency (value) {
return `$${value.toFixed(2)}`
}
}
}
// 후: method 사용
{
methods: {
toCurrency (value) {
return `$${value.toFixed(2)}`
}
}
}
만약 filter
가 글로벌 필터인 경우에는 어떻게 해야 할까?
// 글로벌 필터
Vue.filter('toCurrency', function (value) {
return `$${value.toFixed(2)}`
})
이 경우, 필자는 글로벌 필터 코드를 제거하고 순수한 헬퍼(helper) 함수로 만드는 것을 추천한다.
// helper/filter.js
export function toCurrency (value) {
return `$${value.toFixed(2)}`
}
그리고 헬퍼 함수를 필요한 컴포넌트에 가져와서 사용한다.
// price-component.vue
import { toCurrency } from './helper/filter'
// Vue 컴포넌트
{
methods: {
toCurrency
}
}
ES6 객체 프로퍼티 단축 덕분에 그냥 toCurrency
만 정의해도 정상적으로 동작한다.
model
을 .sync
로 리팩토링한다.RFC 문서에 따르면 Vue 3는 Vue 컴포넌트에서 model
옵션이 없어지고, sync
를 사용하지 않아도 v-model
을 여러 개 정의할 수 있다. (역자: Vue 2에서는 양방향 바인딩을 위해 오직 하나의 v-model
만 정의할 수 있다. 그래서 대안책으로 .sync
를 사용하였으나, Vue 3에서는 v-model
을 여러 번 정의할 수 있다.)
양방향 데이터 바인딩을 위해 사용했던 model
옵션을 .sync
로 변경한다.
// 전
// 부모 component
<child-component v-model="visible"/>
// 자식 component의 model 옵션
<script>
{
model: {
prop: 'visible',
event: 'change'
}
}
</script>
// 후
// 부모 component
<child-component v-bind:visible.sync="visible"/>
// 자식 component에서 model 옵션을 제거한다.
추후 Vue 3로 업그레이드할 때, .sync
를 v-model
로 변경하면 된다.
// Vue 3
// 부모 component
<child-component v-model:visible="visible"/>
빠르고 쉽게 리팩토링을 할 수 있다! 😋
다른 프레임워크처럼 Vue는 사용자가 자신만의 플러그인을 만들 수 있도록 API를 제공한다.
하지만 Vue 3의 변화로 인해 특정 플러그인은 더는 Vue 3에서 호환이 되지 않을 예정이다. 주요 변경 사항 중 하나는 플러그인의 설치와 앱 초기화를 Vue 인스턴스에서 할 수 없다는 것이다.
// 전: Vue 2
Vue.use(myPlugin);
new Vue({/* Vue 초기화 */});
// 후: Vue 3
const app = createApp(); // Vue 초기화를 위한 새로운 메서드
app.use(myPlugin);
이러한 변경으로 인해 사용자는 자신의 코드를 수정하더라도 플러그인 개발자가 업그레이드하기 전까지 Vue 3에서 해당 플러그인을 사용할 수 없다. 이는 써드 파티 플러그인이 Vue 3로 마이그레이션하는데 큰 걸림돌이 될 수 있으므로 써드 파티 플러그인 사용을 주의해야 한다는 뜻이다.
사용하고 있는 플러그인의 이슈나 로드맵에서 Vue 3에서 사용할 수 있도록 업그레이드가 예정되어 있는지 확인하자. 아래는 Vue 3 지원을 할 예정이라고 언급한 플러그인이다.
만약 사용하고 있는 플러그인에서 Vue 3를 지원할 계획이 없다면, 이슈를 등록하여 Vue 3를 지원해달라고 요청하거나 플러그인 개발자를 도와 기여를 하는 방법도 있다. 🤗
@vue/composition-api
를 사용한다.필자는 @vue/composition-api
를 제공해준 Vue 커뮤니티에 매우 감사한다 🥰. @vue/composition-api
는 개발자들에게 컴포지션(Composition) API를 손쉽게 사용할 수 있게 해주고, Vue 3의 핵심 메서드가 될 API도 제공한다.
한 가지 예로 defineComponent
가 있다. defineComponent
는 Vue 3에서 Vue 컴포넌트를 작성할 새로운 기준이 될 것이며 Vue 2 앱에서 이미 사용해볼 수도 있다!
@vue/composition-api
를 설치하고 defineComponent
를 이용하여 Vue 컴포넌트를 초기화하도록 변경한다.
// 전
import Vue from 'vue';
export default Vue.extend({
name: 'my-component',
/* props, data, methods, ... */
});
// 후
import { defineComponent } from '@vue/composition-api';
export default defineComponent({
name: 'my-component',
/* your component props, data, methods, etc. */
});
사실상 쓰는데는 거의 차이가 없다! Vue 컴포넌트는 이전과 동일하게 동작하며 심지어 컴포지션 API를 이용하여 리팩토링할 수 있는 "보너스"를 얻었다.
// setup()을 이용한 리팩토링
import { defineComponent } from '@vue/composition-api';
export default defineComponent({
name: 'my-component',
setup (props) {
/* props, data, methods, ... */
}
});
만약 TypeScript를 사랑한다면 컴포지션 API 역시 사랑하게 될 것이다. 컴포지션 API는 컴포넌트를 작성하는 환경을 개선해주기 때문이다. ;)
아래는 또 다른 주요한 변화이다.
하지만 만약 이런 코드를 많이 사용하지 않았거나, 리팩토링이 쉬우리라 생각되면 Vue 3로 업그레이드 해보는 것도 좋을 것이다. 결정은 당신에게 달렸다.
휴! 필자는 이 글이 Vue 3로 업그레이드할 준비를 하는 데 도움이 되었으면 한다. Vue 3에서는 반응형을 조작할 수 있는 발전된 API를 사용할 수 있고, TypeScript 지원이 개선되었으며, 더 나은 방법으로 개발할 수 있다. 필자는 Vue 개발자로서 Vue 3로 업그레이드할 날이 얼른 다가오기를 기대하고 있다.
만약 필자가 API나 설명을 빠뜨렸다면 피드백을 주시길 부탁한다. 필자는 독자들의 피드백에 언제나 감사한다.