Component setup using ES6 class works out of the box in Vue 3, why not officially support it!
What problem does this feature solve?
I just discovered that we could use ES6 class for creating components instead of the Composition API setup function. The best thing is it works out of the box in Vue 3 without any dependency on vue-class-component
or other libraries! Even the experimental template interpolation service in Vetur is working flawlessly with it!!!
Surprisingly I couldn't find this way of creating components in any of Vue 3 documentation. Not sure why, or am I missing something!? With official support, we could make the experience even better.
Why use ES6 class:
In the current way of creating a component using setup function & composition API, users are encouraged to create closure functions. When the number of instances of a component is minimal this doesn't make much difference. But when there are so many instances of the component, multiples of those many functions objects would be created, which doesn't look ideal.
For instance, 1000 instances of the following component would create 1000 getUserRepositories
function objects. Which is overkill.
import { fetchUserRepositories } from '@/api/repositories'
setup (props) {
let repositories = []
const getUserRepositories = async () => {
repositories = await fetchUserRepositories(props.user)
}
return {
repositories,
getUserRepositories
}
}
What does the proposed API look like?
This is how we could create a Counter component using ES6 class & composition API. And it works with the current release of Vue 3!
<template>
<div class="counter">
Counter: {{ valueInternal }}<br />
<button type="button" @click="onAdd">Add</button>
<button type="button" @click="onSubtract">Sub</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, Ref } from "vue";
class Counter {
valueInternal: Ref<number>;
constructor(val: number) {
this.valueInternal = ref(val);
// Guess following binding & convertion to own properties are needed because of hasOwn check @ https://github.com/vuejs/vue-next/blob/master/packages/runtime-core/src/componentPublicInstance.ts#L293
// We must be able to remove it with official support
this.onAdd = this.onAdd.bind(this);
this.onSubtract = this.onSubtract.bind(this);
}
onAdd() {
this.valueInternal.value++;
}
onSubtract() {
this.valueInternal.value--;
}
someOtherMethod() {
// Statements
}
}
export default defineComponent({
props: {
value: Number
},
setup: props => new Counter(props.value || 0)
});
</script>
I have committed a dummy project with working example @ https://github.com/sreenaths/vue3-composition-class
What could we do through official support:
There could be more, but the following are some of the items that came to my mind.
- Direct default export of the class from inside the script tag.
export default Counter
instead of this working snippetexport default defineComponent(props => new Counter(props));
. - Some way to bypass hasOwn check explained above in Counter constructor so that inherited properties can be accessed from inside the template - Can we use decorators for that?
- Should we have an interface for type-checking?
- When directly exporting the Class, can we provide some way to pass props definition?
- Documentation.
Hey @sreenaths
thanks for doing this write-up! It's kinda cool that this works out of the box for the most part.
However, I don't see a real chance for this to become part of core.
Apart from style-preferences, the only advantage that you can bring up is that of avoiding duplicated functions because of their closures. You seem to think that we can avoid this by using Classes and their prototype inheritance.
But unfortunately, that's not the case. Binding class methods to this
(.bind(this)
) is necessary regardless of the hasOwn
check you point to, because we usually // often want to pass these methods as standalone functions to i.e. v-on
as listeners. Without binding, class methods used outside of their original context lose this
context.
And .bind
create a new function in memory, so the advantage that your perceive in terms of memory doesn't exist.
Besides that (invalid) advantage, using a class has no real objective benefits over plain functions that I can see. So there's really no reason to burden users and the docs with another style of doing things.
If you want to continue this discussion regardless of the points I raised, or have new ones to bring up, please use the discussion
section in the rfcs repo.
Thanks @LinusBorg
I agree that I should have demonstrated it without using bind. The intention behind the following lines was just to bypass hasOwn check. Technically .bind is irrelevant
.
this.onAdd = this.onAdd.bind(this);
this.onSubtract = this.onSubtract.bind(this);
Following is the same implementation without using bind, and it works. Im using Object.assign to bypass hasOwn check.
class Counter {
valueInternal: any;
constructor(val: number) {
this.valueInternal = ref<number>(val);
// Guess this is needed because of hasOwn check @ https://github.com/vuejs/vue-next/blob/master/packages/runtime-core/src/componentPublicInstance.ts#L293
// We must be able to remove it with official support
Object.assign(this, {
onAdd: this.onAdd,
onSubtract: this.onSubtract
});
}
onAdd() {
this.valueInternal++;
}
onSubtract() {
this.valueInternal--;
}
someOtherMethod() {
// Statements
}
}
Thanks for pointing to the discussion section.
Started discussion @ https://github.com/vuejs/rfcs/discussions/276
I think you kind of missed the point about how bind()
is required regardless of hasOwn
.
Here's a plan JS demo:
class Store {
constructor() {
this.data = 'hello'
Object.assign(this, {
setData: this.setData
})
}
setData(v) { this.data = v }
}
const store = new Store()
const setData = store.setData
setData('goodbye') // TypeError: Cannot set property 'data' of undefined
console.log(store.data)
without binding the function (which creates a new function in memory), you can't use methods decoupled from the instance - which is something we need to be able to do in order to pass one of these methods as a listener to a child component, for example (and other use cases).
also posted this to the discussion thread, so we can continue there.