Vue2와 Vue3의 차이
※ 일본의 한 블로그 글을 번역한 포스트입니다. 오역 및 의역, 직역이 있을 수 있으며 틀린 내용은 지적해주시면 감사하겠습니다.
이 포스트에서는 Vue2와 Vue3의 차이점을 몇 가지 픽업해서 간단하게 소개하고자한다.
Composition API
Vue2와의 가장 큰 변경점이라고 한다면 Composition API가 도입된 것이다. 이로 인해 Vue의 <script> 부분의 기재 방법이 크게 변하게 됐다.
CompositionAPI는 Vue2에서도 확장기능으로 도입하는 것이 가능하긴하지만 Vue2에서는 기본적으로 Options API를 이용하는 경우가 많다. Options API에서는 오브젝트 속성으로서 data나 methods등의 역할마다 정리해서 기재한다. 예를 들면 다음과 같은 방식으로 말이다.
<script>
export default {
data: () => ({
count: 0,
})
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
}
}
}
</script>
그러나 Composition API를 사용하면 아래와 같이 바뀐다.
<script setup>
import { ref } from 'vue';
const count = ref(0);
const increment = () => count.value++;
const decrement = () => count.value--;
</script>
상세한 내용에 대해서 설명하자면 길어지므로 다음에 기회가 된다면 구체적으로 설명하도록 하겠다.
Fragments
vue2에서는 컴포넌트의 루트 요소가 1개일 필요가 있었다.
<template>
<div>
<header>...</header>
<main>...</main>
<footer>...</footer>
</div>
</template
Vu3에서는 여러 개의 요소를 루트 요소로 지정할 수 있다.
<template>
<header>...</header>
<main>...</main>
<footer>...</footer>
</template>
글로벌 API
Vue 인스턴스의 생성방법이 글로벌 API를 사용하는 방법으로 변한다. CDN을 이용하는 경우는 아래와 같이 달라진다. 위에가 Vue2, 아래가 Vue3이다.
new Vue({
el: '#app',
...
})
Vue.createApp({
...
}).mount('#app')
그 외 Vue2에서는 글로벌 API로서 정의되어 있던 것이 Vue3에서 부터는 인스턴스 API로써 정의된다.
data의 선언
CDN을 이용한 경우 Vue2에서는 data를 오브젝트로 선언할 수 있었지만, Vue3에서는 함수 선언으로 바뀐다. 보통 Vue 파일을 이용하고 있는 경우, 큰 영향이 없는듯하다.
Vue.createApp({
data: () => ({
...
})
}).mount('#app')
라이프 사이클
Vue3에서는 라이프 사이클의 일부 명칭이 변경된다. 역할 자체는 변경되지 않는다.
- beforeDestroy → beforeUnmount
- destroy → unmounted
트랜지션 클래스
<transition>으로 정의된 클래스명이 일부 바꼈다.
- v-enter → v-enter-from
- v-leave → v-leave-from
SCSS를 사용하고 있는 경우 이쪽이 가독성이 좋다.
fillter
Vue3에서는 fillter가 이용할 수 없게 된다. 사실 computed로 충분하기 때문에 큰 문제는 없을 것 같다.
emits
Vue2에서는 아래와 같은 $emit을 사용하여 커스텀 이벤트를 설정했었다.
<template>
<button @click="$emit('myclick')">Click Me!!</button>
</template>
<template>
<my-component @myclick="onClick" />
</template>
<script>
export default {
methods: {
onClick() {
//...
}
}
}
</script>
Vue3에서는 위 기재로는 부족하며, 새롭게 추가된 emits 속성을 사용하여 커스텀 이벤트를 모두 지정할 필요가 있다.
<template>
<button @click="$emit('myclick')">Click Me!!</button>
</template>
<script>
export default {
emits: ['myclick']
}
</script>
또한 $emit으로 호출했을 때에 그 이벤트가 유효인지를 검증할 수 있다. 아래는 인수가 문자열인지를 확인하는 코드이다.
<script>
export default {
emits: {
myEvent: (text) => {
return typeof text === 'string'
}
},
methods: {
doMyEvent1() {
this.$emit('myEvent', 'TEST') // 이것은OK
},
doMyEvent2() {
this.$emit('myEvent', 111) //이것은NG(경고는 표시된다)
}
}
}
</script>
v-model (쌍방향 바인딩)
한 개의 값
Vue2에서는 v-model의 값이 value 속성과 연결되어 input이벤트로 값의 변경을 한다.
<template>
<button @click="onClick">change</button>
</template>
<script>
export default {
props: {
value: String
},
methods: {
onClick() {
this.$emit('input', 'changed!!')
}
}
}
</script>
<template>
<div>
{{ text }}
<my-component v-model="text" />
</div>
</template>
<script>
export default {
data: () => ({
text: 'no change'
})
}
</script>
Vue3부터는 value가 아닌 modelValue로 연결시키도록 되어 있어 값의 변경은 input이 아닌 update:modelValue를 사용한다. 또한 위에 기재했듯, emits의 지정이 필요로한다는 점이 주의해야할 부분이다.
호출원의 변경은 없다.
<template>
<button @click="onClick">change</button>
</template>
<script>
export default {
props: {
modelValue: String // ← value에서 변경
},
emits: ['update:modelValue'], // ← 이것을 추가
methods: {
onClick() {
this.$emit('update:modelValue', 'changed!!') // ← input에서 변경
}
}
}
</script>
여러 개의 값
Vue2에서는 여러 개의 값을 쌍방향 바인딩할 경우 v-model이 아닌 sync수식자를 사용한다. 값의 변경은 update:[속성명]을 사용했다.
<template>
<div>
<button @click="onClick1">change text1</button>
<button @click="onClick2">change text2</button>
</div>
</template>
<script>
export default {
props: {
text1: String,
text2: String
},
methods: {
onClick1() {
this.$emit('update:text1', 'changed text1!!')
},
onClick2() {
this.$emit('update:text2', 'changed text2!!')
}
}
}
</script>
<template>
<div>
<p>{{ text1 }}</p>
<p>{{ text2 }}</p>
<my-component :text1.sync="text1" :text2.sync="text2" />
</div>
</template>
<script>
export default {
data: () => ({
text1: 'no change',
text2: 'no change'
})
}
</script>
Vue3에서는 sync 수식사를 사용할 수 없게 되며, 대신에 v-model:[속성명]을 사용한다. 컴포넌트쪽의 정의는 emits를 추가하는 것 외에는 변한게 없다.
<template>
<div>
<button @click="onClick1">change text1</button>
<button @click="onClick2">change text2</button>
</div>
</template>
<script>
export default {
props: {
text1: String,
text2: String
},
emits: ['update:text1', 'update:text2'], // ← 이 부분의 추가하면됨
methods: {
onClick1() {
this.$emit('update:text1', 'changed text1!!')
},
onClick2() {
this.$emit('update:text2', 'changed text2!!')
}
}
}
</script>
<template>
<div>
<p>{{ text1 }}</p>
<p>{{ text2 }}</p>
<!-- v-model으로 변경 -->
<my-component v-model:text1="text1" v-model:text2="text2" />
</div>
</template>
<script>
export default {
data: () => ({
text1: 'no change',
text2: 'no change'
})
}
</script>
수식자
v-model의 원래 .trim이나 .number와 같은 수식자가 있었다.
- .trim : 값의 양쪽 끝 공백을 제거
- .number : 값을 수치로 변환
<template>
<my-component v-model.trim="text" />
</template>
<script>
export default {
data: () => ({
text: ''
})
}
</script>
Vue3에서는 이것에 더해 커스텀 수식자를 정의할 수 있다. 정확히는 어떠한 수식자가 지정되어 있는가를 modleModifiers 속성으로 받을 수 있다.
<script>
export default {
props: {
modelValue: String,
modelModifiers: { // ← 이것으로 받는다.
default: () => {}
}
}
}
</script>
<template>
<p>{{ text }}</p>
<my-component v-model.capitarize.reverse="text" />
</template>
<script>
export default {
data: () => ({
text: ''
})
}
</script>
위 기재의 경우, modelModifiers는 아래와 같이 지정된 수식자를 키로 가진 객체가 된다.
modelModifiers = {
'capitarize': true,
'reverse': true
}
이 값을 바탕으로 값을 갱신할 때 처리를 추가한다. 아래는 capitarize가 지정되어 있을 경우 문자의 선두를 대문자로, reverse가 지정된 경우 문자를 반전해 값을 갱신하고 있다.
<script>
export default {
props: {
modelValue: String,
modelModifiers: {
default: () => {}
}
},
emits: ['update:modelValue'],
computed: {
val: {
get() {
return this.modelValue
},
set(val) {
let v = val
// 맨앞의 글자를 대문자로
if (this.modelModifiers.capitarize) {
v = v.charAt(0).toUpperCase() + v.slice(1)
}
// 문자열을 반전
if (this.modelModifiers.reverse) {
v = v.reverse()
}
this.$emit('update:modelValue', v)
}
}
}
}
</script>
v-model:text처럼 속성을 지정하는 경우 textModifier와 같이 [속성 이름] + Modifier로 수식자 지정 정보를 받는다.
Teleport
Teleport는 Vue3 신기능이다. <teleport>태그로 감싸진 요소를 지정한 요소내에 이동시키는 것이 가능하다. 예시는 다음과 같다.
<template>
<teleport to="body">
<div class="my-modal" v-if="show">
<div class="my-modal__contents">
<slot />
</div>
</div>
</teleport>
</template>
<script>
export default {
props: {
show: {
type: Boolean,
default: false
},
},
}
</script>
<template>
<button @click="show = true">표사</button>
<my-modal :show="show">모달의 컨텐츠</my-modal>
</template>
<script>
export default {
data: () => ({
show: false
})
}
</script>
참고자료