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.
- Safety. Transform es class to vue option api according to specifications.
- Performance. Once transform on project loading, ready for everywhere.
- Support both es class inherit and vue component extending.
Welcome to suggest and contribute. Message me on github.
Install
npm install -S vue-facing-decorator
How to use
Index
Basic
import {
Component,
Ref,
Watch,
Prop,
Inject,
Emit,
Base,
} from "vue-facing-decorator";
import AnotherComponent from "./AnotherComponent.vue";
//super class. See extends section.
class Sup extends Base {
//reactivity super property
supProperty = "supProperty";
//super method
supMethod() {}
//super getter
get supGetter() {
return "supGetter";
}
}
//component class
@Component({
//OPTION, component name
name: "MyComponent",
//OPTION, emits
emits: ["update:modelValue"],
//OPTION, provide object or function(this:Comp){return {foo:'bar'}}
provide: {
provideKey: "provideValue",
},
//OPTION, components
components: {
AnotherComponent,
},
//OPTION, inheritAttrs
inheritAttrs:true,
//OPTION, expose
expose:[],
//OPTION, directives
directives:{
},
//OPTION, this will be assigned to vue option
options: {
beforeRouteEnter() {
},
},
//OPTION, use modifier to modify component option built by vue-facing-decorator
modifier: (option: any) => {
console.log("generated optoin", option);
option.methods ??= {};
option.methods.method2 = function () {
console.log("method2");
};
return option;
},
})
export default class Comp extends Sup {
//emit an event with name of method
@Emit()
eventName(arg: any) {
return arg;
}
//emit an event with custom name and promise value
//event will be emitted when promise resolved
@Emit("eventCustomNamePromise")
event2(arg: any) {
return new Promise((resolver) => {
resolver(arg);
});
}
//create a ref
@Ref
readonly ref!: HTMLDivElement;
//create a prop
@Prop({
//prop options
required: true,
default: "default prop",
type: String,
validator(v: string) {
console.log("prop validator", v);
return true;
},
})
readonly prop!: string;
//v-model default prop
@Prop({
required: true,
type: Number,
})
readonly modelValue!: number;
//reactivity property
property = "property";
//getter
get getter() {
return "getter";
}
//method
method() {
//call vue api
this.$forceUpdate();
//set reactivity property
this.property += ">";
//trigger update v-model
this.$emit("update:modelValue", this.modelValue + 1);
}
//create a watcher
@Watch("property", {
//watcher options
deep: true,
immediate: true,
flush: "post",
})
propertyWatcher(newv: string, oldv: string) {
console.log("property changed", newv, oldv);
}
//inject from acient components
@Inject({
//inject options
default: "defult value",
from: "provideAcientKey",
})
provideAcientKeyAlias!: string;
mounted() {
//vue lifecycle
console.log(
this.ref,
this.getter,
this.property,
this.supProperty,
this.supGetter,
this.prop,
this.provideAcientKeyAlias
);
this.eventName("eventName value");
this.event2("eventCustomNamePromise value");
}
}
is equal to
import { defineComponent} from "vue";
import AnotherComponent from "./AnotherComponent.vue";
export default defineComponent({
name: "MyComponent",
components: {
AnotherComponent,
},
emits: ["update:modelValue", "eventName", "eventCustomNamePromise"],
provide: {
provideKey: "provideValue",
},
inject: {
provideAcientKeyAlias: {
default: "defult value",
from: "provideAcientKey",
},
},
data() {
return {
supProperty: "supProperty",
property: "property",
};
},
methods: {
supMethod() {},
method() {
this.$forceUpdate();
this.$emit("update:modelValue", this.modelValue + 1);
},
method2() {
console.log("method2");
},
eventName() {
this.$emit("eventName", "eventName value");
},
async event2() {
const value = await new Promise<any>((resolver) => {
resolver("eventCustomNamePromise value");
});
this.$emit("eventCustomNamePromise", value);
},
},
watch: {
property: function (newv: string, oldv: string) {
console.log("property changed", newv, oldv);
},
},
computed: {
supGetter() {
return "supGetter";
},
getter() {
return "getter";
},
ref() {
this.$refs["ref"];
},
},
props: {
prop: {
required: true,
default: "default prop",
type: String,
validator: function (v: string) {
console.log("prop validator", v);
return true;
} as any, // type error
},
modelValue: { type: Number, required: true },
},
mounted() {
console.log(
this.ref,
this.property,
this.supProperty,
this.getter,
this.supGetter,
this.prop,
(this as any).provideAcientKeyAlias //type error
);
},
beforeRouteEnter() {},
});
Extends
import { Component, ComponentBase, Base } from 'vue-facing-decorator'
//Comp1 super class
class Comp1Sup extends Base {
method1Sup() {
return 'method1Sup value'
}
}
/*
Comp1 base component. To define a base component use `@ComponentBase` instead of `@Component`.
Runtime will bundle Class `Comp1` and `Comp1Sup` to a vue option component.
Methods of Comp1 will override `Comp1Sup`'s.
Decorators can be only used on `@Component` and `@ComponentBase` classes. Comp1Sup don't accept any decorators.
*/
@ComponentBase
class Comp1 extends Comp1Sup {
method1Comp() {
return 'method1Comp value'
}
}
class Comp2Sup extends Comp1 {
method2Sup() {
return 'method2Sup value'
}
}
/*
Similer to Comp1, runtime will bundle class `Comp2` and `Comp2Sup` into a vue option component.
Bundled `Comp2` will extends bundled `Comp1` by vue `extends` option `{extends:Comp1}`
*/
@ComponentBase
class Comp2 extends Comp2Sup {
method2Comp() {
return 'method2Comp value'
}
}
class Comp3Sup extends Comp2 {
method3Sup() {
return 'method3Sup value'
}
}
/*
(Comp3 -> Comp3Sup) vue extends (Comp2 -> Comp2Sup) vue extends (Comp1 -> Comp1Sup)
Class extends class by ES class extending strategy i.e. `Comp3 -> Comp3Sup` .
Vue component extends vue component by vue component exteding strategy i.e. `(Comp3 -> Comp3Sup) vue extends (Comp2 -> Comp2Sup)`
`Comp3` is a "Final Component" decorated by '@Component'.
*/
@Component
export default class Comp3 extends Comp3Sup {
method3Comp() {
return 'method3Comp value'
}
}
is euqal to
import { defineComponent } from 'vue';
export default defineComponent({
extends: {
extends: {
methods: {
method1Comp() {
return 'method1Comp value'
}
}
},
methods: {
method2Comp() {
return 'method2Comp value'
}
}
},
methods: {
method3Comp() {
return 'method3Comp value'
}
}
})
Tsx render
//in Comp.render.tsx
import type Comp from './Comp'
export default function render(this: Comp) {
return <div onClick={this.onClick}>Tsx render {this.number}</div>
}
//in Comp.ts
import {
Component,
Base,
} from 'vue-facing-decorator'
import render from './Comp.render'
@Component({
render
})
export default class Comp extends Base {
number = 1
onClick() {
this.number++
}
}
//in parent component
import {
Component,
Base,
} from 'vue-facing-decorator';
import Comp from "./Comp"
@Component({
components:{
Comp
}
})
export default class ParentComponent extends Base {
}
is euqal to
//in Comp.ts
import { defineComponent } from "vue";
import render from './Comp.render'
export default defineComponent({
render,
data(){
return {
number:1
}
},
methods:{
onClick(){
this.number++
}
}
})
In class lifecycle names
These class names could be defined in class directly.
[
"beforeCreate",
"created",
"beforeMount",
"mounted",
"beforeUpdate",
"updated",
"activated",
"deactivated",
"beforeDestroy",
"beforeUnmount",
"destroyed",
"unmounted",
"renderTracked",
"renderTriggered",
"errorCaptured",
"serverPrefetch"
]
For names not in this list, use
Component({
options:{
foo(){
}
}
})
or
@Component({
modifier(opt:any){
opt.foo=function(){}
return opt
}
})