It is with a huge disappointment that I almost stopped using Vue, because TypeScript support is oriented towards the Composition API, abandoning Class Components. Fortunately, all is not lost!
TL;DR
# create a Vite based project (official way)
npm init vue@3
# install vue-facing-decorator
# cd [to the project], then
npm -i --save vue-facing-decorator
<template>
<h2>{{ title }}</h2>
<span>{{ message }}</span>
<button @click="onStuff">Click me</button>
</template>
<script>
// use the modul to import Component and Vue
import { Component, Prop, Vue } from "vue-facing-decorator";
// and you can now use class component in Vue3 / Vite projects
@Component
export default class MyComponent extends Vue {
// Property from the component tag
@Prop message!: String
// public methods and properties can be
// acccessed from the template too
title = "Test"
onStuff() {
// example of handler...
}
}
</script>
No need of "hooks", it's pure TS.
I love TS, and I love Vue
I develop, among other things, Web applications with mainly 3 frameworks. One of which leaves me speechless:
- Vue
- Angular
- React (I don't like...)
You notice that I did not suffix the names with "JS", because I use mostly TypeScript.
I use TypeScript for a reason, I like a component to be defined as a class, to have properties and methods, and to use the principles of encapsulation and variable scope. So, in essence, I like OOP, and I'm absolutely a fan of having type checking within TypeScript. It makes the code much more secure.
Previously, using vue-cli
to create my projects, I was able to activate TypeScript and "class component" syntax. I was so happy.
So, Vue with TypeScript was closed to what I can do in Angular:
<script>
// with vue-class-component
@Options
export default class MyComponent extends Vue {
myData = "foo"
myMehtod() {
// code here...
}
}
</script>
Everything was fine until this thunderclap...
Today, vue-cli
is deprecated to create a new project. The new way is to use Vite
and it does'nt provides vue-class-component
module and there is no option to force the "old way".
And adding the module fails if the project is Vite based.
Vue(TS) is abandoning
vue-class-component
, preferring to ask developers to use the Composition API.
See https://github.com/vuejs/vue-class-component/issues/569
And, if you're not sure that Vue doesn't like class (exactly like React, and it's a shame in my opinion), read this:
https://vuejs.org/guide/extras/composition-api-faq.html#will-options-api-be-deprecated
So... Here is the "new appreciated way to do" so far:
<script setup lang="ts">
// now...
import { reactive, ref } from "vue";
const myData = ref("foo") // or raeactive()
function myMethod() {
// code here
}
</script>
What's the problem dude?
You probably think it's cool. It's easier to write the component logic for those who used to work in JS.
And, be honnest, it's now closed to what we can see in React.
And I don't like React. I've got many reasons that I will expose in a future article. But the substance is this:
We lose the notion that a component is an "object". We also need to call functions to declare that a variable is reactive, and we need a "JS" syntax to create
computed
functions for example.
That's not pretty.
To illustrate what I mean:
// This is somehting I prefer
@Component
class Example extends Vue {
private msg = "Hello";
title = "The title";
aMethod(){
// this method can be called in my template
}
// make a computed "message" variable
set message(val: String) {
this.msg = val.trim();
}
get message():String {
return this.msg;
}
}
// This is what to do in a "setup" tag
import { computed, ref } from "vue";
const msg = "Hello";
const title = ref("The Title");
// make a computed "message" variable
const message = computed({
set(val: String) => {msg = val.trim()},
get():String => this.msg
});
Of course, there is less of code lines. But the readability is bad (my opinion, of course).
The good paradigm I have in mind is that a component is an object that I can instantiate. So, it must be a class.
If I want a poor support of TypeScript, using hooks or functions to declare typed objects, reactive (or ref for primitives) or computed variables, so I can use React... And, as said earlier, I don't like React.
The solution!
By chance, I'm not the only one who loves to work with TypeScript using the strongness of the langage.
There is a fantastic module named vue-facing-decorator
here: https://facing-dev.github.io/vue-facing-decorator/
facing-dev / vue-facing-decorator
Vue typescript class component decorators
Read me
Designed for vue 3, do the same work like vue-class-component and vue-property-decorator.
- Community desired vue class component with typescript decorators.
- Compatible with both stage3 and stage2 decorators.
- Compatible with both TypeScript and JavaScript projects.
- Safety. Transform ES class to vue option api according to specifications.
- Performance. Once transform on project loading, ready for everywhere.
- Support ES class inheritance, vue
extends
and vuemixins
. - Official recommended.
Welcome to suggest and contribute.
Donate
Document
Document languages: English, 简体中文, Portuguese
Wellknown issues
https://facing-dev.github.io/vue-facing-decorator/#/en/wellknown-issues/wellknown-issues
Discussion
To discord https://discord.gg/4exxtFgkcz
QQ群号 766350254
Awesome List
-
Binding helpers for Vuex and vue-facing-decorator.
And it's very easy to use.
Create a project with TypeScript support:
npm init vue@3
# ==> and select "TypeScript" support in the wizzard
Then, inside the project, install the module:
npm i --save vue-facing-decorator
In the tsconfig.json
file, there is a section named compilerOptions
, only add this:
{
"compilerOptions": {
"experimentalDecorators": true
}
}
And now, you can import
Component
decorator, andVue
base class fromvue-facing-decorator
. Easy 😄
How to use?
It's pretty simple. Create a component like this:
// example, TodoItem.vue
<template>
<li> <input type="checkbox" v-model="done" /> {{ title }}</li>
</template>
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
@Component
export default class TodoItem extends Vue {
done = false
title = ""
}
</script>
Of course, you can use properties, for example to send a "Todo" object in the <TodoItem :todo="todo" />
component:
// example, TodoItem.vue
<template>
<li>
<input
type="checkbox"
v-model="todo.done" />
{{ todo.title }}
</li>
</template>
<script lang="ts">
import Todo from "@/classes/todo";
import { Component, Vue } from "vue-facing-decorator";
@Component
export default class TodoItem extends Vue {
@Prop todo!: Todo;
}
</script>
Computed are simple getters and setters (with get
and set
keywords), methods are simple methods, ...
You can see the documentation to see how to use the others decorators (like @Watch
for example) and the options that you can pass to them.
Conclusion
I have absolutely nothing against the fact that you may like the Vue Composition API. It's just a way of coding that doesn't suit me. I have a clear preference for Angular precisely because this Framework uses TypeScript in the most beautiful way.
Vue, with vue-class-component
brought me the best of both worlds. Beautiful TypeScript, with the ease inherent to Vue. The abandonment of this module almost drove me away.
But the vue-facing-decorator
module makes up for it in a big way. And I thank the authors of this module.
Note: There is also another project that provides class component at setup stage. I'm not satisfied, but maybe you can appreciate: https://github.com/fmfe/vue-class-setup
Top comments (3)
Thanks for this! We ran into a lot of these same considerations about Vue 3:
Vue 2 to Vue 3 with class components
How does it compare to vue-demi?
Vue-demi is not made for the same goal. It is not (AFAIK) made for TypeScript and it doesn't propose class API.