Summary
- Composition functions are available in class property initializers by wrapping
setup helper.
- Class property initializers are handled in
setup function under the hood.
- Only
$props (and its derived prop values), $attrs, $slots and $emit are available on this in class property initializers.
Example:
<template>
<div>Count: {{ counter.count }}</div>
<button @click="counter.increment()">+</button>
</template>
<script lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { Vue, setup } from 'vue-class-component'
function useCounter () {
const count = ref(0)
function increment () {
count.value++
}
onMounted(() => {
console.log('onMounted')
})
return {
count,
increment
}
}
export default class Counter extends Vue {
counter = setup(() => useCounter())
}
</script>
Details
Prior to v7, class component constructor is initialized in data hook to collect class properties. In v8, it will be initialized in setup hook so that the users can use composition function in class property initializer.
The above class component definition is as same as following canonical component definition.
function useCounter () {
const count = ref(0)
function increment () {
count.value++
}
onMounted(() => {
console.log('onMounted')
})
return {
count,
increment
}
}
export default {
setup() {
return { counter: useCounter() }
}
}
setup helper
Wrapping a composition function with setup helper is needed because we need to delay the invocation of the composition function. Let's see the following example:
function usePost(postId) {
const post = ref(null)
watch(postId, async id => {
post.value = await fetch('/posts/' + id)
}, {
immediate: true
})
return {
post
}
}
class App extends Vue {
postId = '1'
// DO NOT do this
post = usePost(toRef(this, 'postId'))
}
In the above example, this.postId will be referred by watch helper to track reactive dependencies immediately but it is not reactive value at that moment. Then the watch callback won't be called when postId is changed.
setup helper will delay the invocation until this.postId become a proxy property to the actual reactive value.
setup unwrapping
As same as setup in the Vue core library, setup in Vue Class Component unwraps ref values. The unwrapping happens shallowly:
// The returned value is:
// {
// count: { value: 0 },
// nested: {
// anotherCount: { value: 1 }
// }
// }
function useCount() {
const count = ref(0)
const anotherCount = ref(1)
return {
count,
nested: {
anotherCount
}
}
}
class Counter extends Vue {
// counter will be:
// {
// count: 0, <-- unwrapped
// nested: {
// anotherCount: { value: 1 }
// }
// }
// The shallow ref (count) is unwrapped while the nested one (anotherCount) retains
counter = setup(() => useCount())
}
In addition, if you return a single ref in setup helper, the ref will also be unwrapped:
// The returned value is: { value: 42 }
function useAnswer() {
const answer = ref(42)
return answer
}
class Answer extends Vue {
// answer will be just 42 which is unwrapped
answer = setup(() => useAnswer())
}
Available built in properties on this
Since the class constructor is used in setup hook, only following properties are available on this.
$props
- All props are proxied on
this as well. (e.g. this.$props.foo -> this.foo)
$emit
$attrs
$slots
Example using $props and $emit in a composition function.
function useCounter(props, emit) {
function increment() {
emit('input', props.count + 1)
}
return {
increment
}
}
export default class App extends Vue {
counter = setup(() => {
return useCounter(this.$props, this.$emit)
})
}
Alternative Approach
Another possible approach is using super class and mixins.
import { ref } from 'vue'
import { setup } from 'vue-class-component'
const Super = setup((props, ctx) => {
const count = ref(0)
function increment() {
count.value++
}
return {
count,
increment
}
})
export default class App extends Super {}
Pros
- Can define properties directly on
this.
Cons
Summary
setuphelper.setupfunction under the hood.$props(and its derived prop values),$attrs,$slotsand$emitare available onthisin class property initializers.Example:
Details
Prior to v7, class component constructor is initialized in
datahook to collect class properties. In v8, it will be initialized insetuphook so that the users can use composition function in class property initializer.The above class component definition is as same as following canonical component definition.
setuphelperWrapping a composition function with
setuphelper is needed because we need to delay the invocation of the composition function. Let's see the following example:In the above example,
this.postIdwill be referred bywatchhelper to track reactive dependencies immediately but it is not reactive value at that moment. Then the watch callback won't be called whenpostIdis changed.setuphelper will delay the invocation untilthis.postIdbecome a proxy property to the actual reactive value.setupunwrappingAs same as
setupin the Vue core library,setupin Vue Class Component unwrapsrefvalues. The unwrapping happens shallowly:In addition, if you return a single ref in
setuphelper, the ref will also be unwrapped:Available built in properties on
thisSince the class constructor is used in
setuphook, only following properties are available onthis.$propsthisas well. (e.g.this.$props.foo->this.foo)$emit$attrs$slotsExample using
$propsand$emitin a composition function.Alternative Approach
Another possible approach is using super class and mixins.
Pros
this.Cons
Need duplicated props type definition.