关于vue.js:使用-vueclasssetup-编写-class-风格来的组合式API支持Vue2和Vue3

30次阅读

共计 5262 个字符,预计需要花费 14 分钟才能阅读完成。

前言

我司基于 vue-class-component 开发的我的项目有上百个,其中部署的 SSR 服务也靠近 100 个,如此宏大体量的我的项目一开始的时候还空想着看看是否要降级 Vue3,后果调研一番下来,才发现 vue-class-component 对 Vue3 的反对,最初一个版本公布都过来两年了,迟迟还没有公布正式版本。目前基本上处于无人保护的状态,而且降级存在着大量的破坏性更新,对于将来是否还要持续应用 Vue3 当初还是持保留意见,然而不障碍咱们先把组件库做成 Vue2 和 Vue3 通用,于是就有了本文。

在过来的三年里,vue-class-component最大的问题是就是无奈正确的校验组件的传参,事件类型,这给我带来了微小的暗影,在通过一番调研后,惊喜的发现应用 defineComponent 定义的组件,在 Vue2.7 和 3.x 都能够正确的辨认类型,所以先打算外部的组件库先做到同时反对 Vue2 和 Vue3,如果前面还要持续采纳 Vue3 就变得容易得多。

于是,回到了结尾,调研了一番 vue-class-component 在 Vue3 的反对,目前最新的版本是 8.0.0-rc.1,后果大喜过望,目前基本上处于无人保护的状态,社区内又没有一个能满足我需要的,同时反对 Vue2 和 Vue3 的。

诞生想法

鉴于 vue-class-component 组件目前无奈做到正确的组件类型测验,当我惊喜的发现组合式 API 写进去的代码能够被正确的辨认类型时,诞生了一个应用 class 格调来编写组合式 API 的想法,于是破费一个月的实际,踩遍了所有的坑,终于诞生了 vue-class-setup,一个应用 class 格调来编写代码的库,它 gzip 压缩后,1kb 大小。

疾速开始

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>

尝试多很多种计划,最终采纳了下面的模式为最佳实际,它无奈做到 export default 间接导出一个类,必须应用 defineComponent 来包装一层,因为它只是一个 组合类(API),并非是一个组件。

最佳实际

<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>

多个类实例

在一些简单的业务时,有时须要多个实例

<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

在类实例准备就绪后,PassOnTo 装璜器,会将对应的函数,传递给回调,这样咱们就能够顺利的和 onMounted 等钩子一起配合应用了

import {onMounted} from 'vue';
@Setup
class App extends Define {@PassOnTo(onMounted)
    public onMounted() {}
}

Watch

在应用 vue-property-decoratorWatch 装璜器时,他会接管一个字符串类型,它不能正确的辨认类实例是否存在这个字段,然而当初 vue-class-setup 能查看你的类型是否正确,如果传入一个类实例不存在的字段,类型将会报错

<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

在一些场景,咱们心愿能够裸露组件的一些办法和属性,那么就须要应用 defineExpose 编译宏来定义导出了,所以提供了一个 .use 的类静态方法帮你获取以后注入的类实例

<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>

为什么应用 class?

其实不太想探讨这个问题,喜爱的天然会喜爱,不喜爱的天然会不喜爱,世上本无路,走的人多了,就有了路。

最初

不论是 选项 API 还是 组合式 API,代码都是人写进去的,他人都说 Vue 无奈胜任大型项目,然而在我司的实际中禁受住了实际,基本上没有产生那种数千行的组件代码。

如果喜爱应用 class 格调来编写代码的,无妨来关注一下

  • vue-class-setup

如果你的业务简单,须要应用 SSR 和微服务架构,无妨也关注一下

  • vue-genesis

正文完
 0