Composition functions support and `this` value changes
See original GitHub issueSummary
- Composition functions are available in class property initializers by wrapping
setup
helper.- Class property initializers are handled in
setup
function under the hood.
- Class property initializers are handled in
- Only
$props
(and its derived prop values),$attrs
,$slots
and$emit
are available onthis
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
)
- 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 }
Issue Analytics
- State:
- Created 3 years ago
- Reactions:20
- Comments:26 (13 by maintainers)
Top GitHub Comments
Couldn’t the
setup
function simply be static class function?@ktsn
For me this approach looks too verbose and a little bit strange. Why are we assiging
setup
function tocounter
variable. Why can’t we just use thesetup
function in a similar way as lifecycle hooks?