foreword
Our company has developed hundreds of projects based on vue-class-component
, of which the deployed SSR services are close to 100. At the beginning of such a huge project, I fantasized about whether to upgrade Vue3. After a while, I found out that vue-class-component
support for Vue3. It has been two years since the last version was released, and the official version has not been released yet. At present, it is basically unmaintained, and there are a lot of destructive updates in the upgrade. I still have reservations about whether to continue to use Vue3 in the future, but it does not prevent us from making the component library common to Vue2 and Vue3 first, so There is this article.
In the past three years, vue-class-component
the biggest problem is that the parameter transmission and event type cannot be correctly verified. This has cast a huge shadow on me. After some research, I was pleasantly surprised The components defined using defineComponent
can correctly identify the type in both Vue2.7 and 3.x, so plan the internal component library to support both Vue2 and Vue3 first, if you want to continue later Adopting Vue3 just got a lot easier.
So, I went back to the beginning and did some research vue-class-component
supported by Vue3, the latest version is 8.0.0-rc.1 . And none of them can meet my needs and support both Vue2 and Vue3.
birth idea
In view of the fact that vue-class-component
components cannot currently perform correct component type verification, when I was pleasantly surprised to find that the code written by the composite API can be correctly identified, a class style was born to write the composite API. So I spent a month of practice and stepped through all the pits. Finally, vue-class-setup was born, a library that uses class style to write code. After gzip compression, it is 1kb in size.
quick start
npm install vue-class-setup
<script lang="ts">
import { defineComponent } from 'vue';
import { Setup, Context } from 'vue-class-setup';
// Setup 和 Context 必须一起工作
@Setup
class App extends Context {
private _value = 0;
public get text() {
return String(this._value);
}
public set text(text: string) {
this._value = Number(text);
}
public onClick() {
this._value++;
}
}
export default defineComponent({
// 注入类实例的逻辑
...App.inject(),
});
</script>
<template>
<div>
<p>{{ text }}</p>
<button @click="onClick()"></button>
</div>
</template>
I tried many and many solutions, and finally adopted the above form as the best practice. It cannot be done export default
to directly export a class, you must use defineComponent
to wrap a layer, because it is only A 组合类(API)
, not a component.
Best Practices
<script lang="ts">
import { defineComponent } from 'vue';
import { Setup, Define } from 'vue-class-setup';
// 传入组件的 Props 和 Emit,来让组合类获取正确的 `Props` 和 `Emit` 类型
@Setup
class App extends Define<Props, Emit> {
// ✨ 你可以直接这里定义Props的默认值,不需要像 vue-property-decorator 那样使用一个 Prop 装饰器来定义
public readonly dest = '--';
// 自动转换成 Vue 的 'computed'
public get text() {
return String(this.value);
}
public click(evt: MouseEvent) {
// 发射事件,可以正确的识别类型
this.$emit('click', evt);
}
}
/**
* 这里提供了另外一种在 setup 函数中使用的例子,默认推荐使用 `defineComponent`
* 如果有多个类实例,也可以在 setup 中实例化类
* <script lang="ts" setup>
* const app = new App();
* <\/script>
* <template>
* <div>{{ app.text }}</div>
* </template>
*/
export default defineComponent({
...App.inject(),
});
</script>
<script lang="ts" setup>
// 如果在 setup 中定义类型,需要导出一下
export interface Props {
value: number;
dest?: string;
}
export interface Emit {
(event: 'click', evt: MouseEvent): void;
}
// 这里不再需要使用变量来接收,可以利用 Vue 的编译宏来为组件生成正确的 Props 和 Emit
// ❌ const props = defineProps<Props>();
// ❌ const emit = defineEmits<Emit>();
defineProps<Props>(); // ✅
defineEmits<Emit>(); // ✅
// 这种默认值的定义,也不再推荐,而是直接在类上声明
// ❌ withDefaults(defineProps<Props>(), { dest: '--' });
// ✅ @Setup
// ✅ class App extends Define<Props, Emit> {
// ✅ public readonly dest = '--'
// ✅ }
// Setup 装饰器,会在类实例化时,自动 使用 reactive 包装类,
// 如果你在 setup 手动实例化,则不需要再执行一次 reactive
// const app = reactive(new App()); // ❌
// const app = new App(); // ✅
</script>
<template>
<button class="btn" @click="click($event)">
<span class="text">{{ text }}</span>
<span class="props-dest">{{ dest }}</span>
<span class="props-value">{{ $props.value }}</span>
</button>
</template>
Multiple class instances
In some complex business, sometimes multiple instances are required
<script lang="ts">
import { onBeforeMount, onMounted } from 'vue';
import { Setup, Context, PassOnTo } from 'vue-class-setup';
@Setup
class Base extends Context {
public value = 0;
public get text() {
return String(this.value);
}
@PassOnTo(onBeforeMount)
public init() {
this.value++;
}
}
@Setup
class Left extends Base {
public left = 0;
public get text() {
return String(`value:${this.value}`);
}
public init() {
super.init();
this.value++;
}
@PassOnTo(onMounted)
public initLeft() {
this.left++;
}
}
@Setup
class Right extends Base {
public right = 0;
public init() {
super.init();
this.value++;
}
@PassOnTo(onMounted)
public initLeft() {
this.right++;
}
}
</script>
<script setup lang="ts">
const left = new Left();
const right = new Right();
</script>
<template>
<p class="left">{{ left.text }}</p>
<p class="right">{{ right.text }}</p>
</template>
PassOnTo
After the class instance is ready, the PassOnTo decorator will pass the corresponding function to the callback, so that we can successfully use it with hooks such as onMounted
import { onMounted } from 'vue';
@Setup
class App extends Define {
@PassOnTo(onMounted)
public onMounted() {}
}
Watch
When using the vue-property-decorator
Watch
, it will receive a string type, which cannot correctly identify whether this field exists in the class instance, but now vue-class-setup can check Is your type correct? If you pass in a field that does not exist in a class instance, the type will report an error
<script lang="ts">
import { Setup, Watch, Context } from 'vue-class-setup';
@Setup
class App extends Context {
public value = 0;
public immediateValue = 0;
public onClick() {
this.value++;
}
@Watch('value')
public watchValue(value: number, oldValue: number) {
if (value > 100) {
this.value = 100;
}
}
@Watch('value', { immediate: true })
public watchImmediateValue(value: number, oldValue: number | undefined) {
if (typeof oldValue === 'undefined') {
this.immediateValue = 10;
} else {
this.immediateValue++;
}
}
}
</script>
<script setup lang="ts">
const app = new App();
</script>
<template>
<p class="value">{{ app.value }}</p>
<p class="immediate-value">{{ app.immediateValue }}</p>
<button @click="app.onClick()">Add</button>
</template>
defineExpose
In some scenarios, we want to expose some methods and properties of components, then we need to use defineExpose
compiler macros to define and export, so we provide a .use
class static method to help you Get the currently injected class instance
<script lang="ts">
import { defineComponent } from 'vue';
import { Setup, Context } from 'vue-class-setup';
@Setup
class App extends Context {
private _value = 0;
public get text() {
return String(this._value);
}
public set text(text: string) {
this._value = Number(text);
}
public addValue() {
this._value++;
}
}
export default defineComponent({
...App.inject(),
});
</script>
<script lang="ts" setup>
const app = App.use();
defineExpose({
addValue: app.addValue,
});
</script>
<template>
<div>
<p class="text">{{ text }}</p>
<p class="text-eq">{{ app.text === text }}</p>
<button @click="addValue"></button>
</div>
</template>
Why use class?
In fact, I don’t really want to discuss this issue. People who like it will naturally like it, and people who don’t like it will naturally dislike it. There is no way in the world.
at last
Whether it is an option API or a combined API, the code is written by people. Others say that Vue is not suitable for large-scale projects, but it has withstood the practice in our company's practice, and basically no thousands of lines of component code are generated. .
If you like to use the class style to write code, you may wish to pay attention
If your business is complex and you need to use SSR and microservice architecture, you might as well pay attention
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。