vue-property-decorator + TypeScriptで書かれたVueコンポーネントをscript setupで書き換える
Nuxt3のstable版がリリース間近(2022/06/06現在)ということで、Nuxt.jsでもVue3で書くのがスタンダードになります。
Vue2まではvue-class-componentやvue-property-decoratorを使って、コンポーネントをclassにしてTypeScriptで書くことができていました。
Vue3ではComposition APIによってTypeScriptで記述できるようになっています。
さらにVue.js 3.2 からは <script setup>
構文が使えるようになりました。これによりComposition API を簡潔に記述できるようになります。
ここでは、vue-property-decoratorで書かれたVueコンポーネントを script setup
の構文に書き換えるにあたって、それぞれの記法がどう対応しているのかを見ていきたいと思います。
概観
全体像は以下のようになります。
<template>
<button type="button" @click="handleClick">
{{ text }}
<IconButton />
</button>
</template>
<script lang="ts">
import { Component, Vue, Prop, Emit } from 'vue-property-decorator'
import IconButton from '@/components/IconButton.vue'
@Component({
components: {
IconButton,
},
})
export default class ButtonPrimary extends Vue {
@Prop({ type: String, required: true })
private readonly text!: string
@Emit('clickButton')
handleClick() {}
}
</script>
<template>
<button type="button" @click="handleClick">
{{ text }}
<IconButton />
</button>
</template>
<script lang="ts" setup>
import IconButton from '@/components/IconButton.vue'
interface Props {
text: string
}
defineProps<Props>()
defineEmits<{
(e: 'click-button'): void
}>()
</script>
以下個別に見ていきます。
@Component
class component
<template>
<div>
<ChildComponent1 />
<ChildComponent2 />
</div>
</template>
<script lang="ts">
import ChildComponent1 from './ChildComponent1.vue'
import ChildComponent2 from './ChildComponent2.vue'
@Component({
components: {
ChildComponent1,
ChildComponent2,
},
})
export default class MyComponent extends Vue {
}
</script>
script setup
<template>
<div>
<ChildComponent1 />
<ChildComponent2 />
</div>
</template>
<script setup lang="ts">
import ChildComponent1 from './ChildComponent1.vue'
import ChildComponent2 from './ChildComponent2.vue'
</script>
import文を書くだけでコンポーネントを使用できるようになりました。
Data
class component
<script lang="ts">
@Component
export default class MyComponent extends Vue {
message = 'Hello'
onInput(value: string) {
this.message = value
}
}
</script>
script setup
<script setup lang="ts">
const message = ref('Hello')
const onInput = (value: string) => {
message.value = value
}
</script>
ref
で宣言することでreactiveな値として使用できるようになります。
値にアクセスするには .value
をつける必要があります。
@Prop
class component
<script lang="ts">
@Component
export default class MyComponent extends Vue {
@Prop({ type: String, required: true })
private readonly text!: string
// default値
@Prop({ type: Number, default: 2 })
private readonly num!: number
// Object
@Prop({ type: Object, required: true })
private readonly myObj!: MyObj
}
</script>
script setup
<script setup lang="ts">
interface Props {
text: string
num?: number
myObj: MyObj
}
const props = withDefaults(defineProps<Props>(), {
num: 2,
})
</script>
独自型もきちんと型を付けられるようになり、コンポーネントを利用する側でも補完が効くようになります。
@Emit
class component
<script lang="ts">
@Component
export default class MyComponent extends Vue {
@Emit('clickButton')
handleClick(num: number) {
return num
}
}
</script>
script setup
<script setup lang="ts">
const emits = defineEmits<{
(e:'click-button', num: number): void
}>()
const handleClick = (num: number) => {
emits('click-button', num)
}
</script>
@Watch
class component
<script lang="ts">
@Component
export default class MyComponent extends Vue {
checkBox: boolean = false
@Watch('checkBox')
watchValue(value: boolean) {
if (value) {
alert('checked!!')
}
}
}
</script>
script setup
<script setup lang="ts">
const checkBox = ref(false)
watch(
checkBox,
(value: boolean) => {
if (value) {
alert('checked!!')
}
}
)
</script>
@Ref
class component
<template>
<ChildComponent ref="childComponent" />
<button ref="submitButton">Submit</button>
</template>
<script lang="ts">
import ChildComponent from './ChildComponent.vue'
@Component
export default class MyComponent extends Vue {
@Ref() readonly childComponent!: ChildComponent
@Ref('submitButton') readonly button!: HTMLButtonElement
}
</script>
script setup
<template>
<ChildComponent ref="childComponent" />
<button ref="submitButton">Submit</button>
</template>
<script setup lang="ts">
const childComponent = ref<ChildComponent>()
const submitButton = ref<HTMLButtonElement>()
</script>
同じ変数名のrefで宣言するだけで参照できるようになりました
Computed
class component
<script lang="ts">
@Component
export default class MyComponent extends Vue {
firstName = 'John'
lastName = 'Doe'
get name() {
return this.firstName + ' ' + this.lastName
}
set name(value) {
const splitted = value.split(' ')
this.firstName = splitted[0]
this.lastName = splitted[1] || ''
}
}
</script>
script setup
<script setup lang="ts">
const firstName = ref('John')
const lastName = ref('Doe')
const name = computed({
get: () => firstName + ' ' + lastName,
set: (value) => {
const splitted = value.split(' ')
firstName.value = splitted[0]
lastName.value = splitted[1] || ''
}
})
</script>
Hooks
class component
<script lang="ts">
@Component
export default class MyComponent extends Vue {
mounted() {
console.log('mounted')
}
}
</script>
script setup
<script setup lang="ts">
onMounted(() => {
console.log('mounted')
})
</script>
Vue2 -> 3でのライフサイクルフックの変更についてはこちらを参照
ライフサイクルフック | Vue.js
- setup は beforeCreate と created のライフサイクルで実行されるため、これらのフック内で実行していたコードはsetup内に直接書く
- mounted -> onMounted のように、onXXXXXという名前に変わった
Discussion