-
Notifications
You must be signed in to change notification settings - Fork 433
Description
Summary
- Composition functions are available in class property initializers by wrapping
setuphelper.- Class property initializers are handled in
setupfunction under the hood.
- Class property initializers are handled in
- Only
$props(and its derived prop values),$attrs,$slotsand$emitare available onthisin 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
thisas well. (e.g.this.$props.foo->this.foo)
- All props are proxied on
$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
-
Need duplicated props type definition.
// Props type definition for setup interface Props { foo: string } const Super = setup((props: Props) => { /* ... */ }) export default class App extends Setup { // Another definition for foo prop to use it in the class @Prop foo!: string }