前言
我司基于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 必须一起工作@Setupclass 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` 类型@Setupclass 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';@Setupclass Base extends Context { public value = 0; public get text() { return String(this.value); } @PassOnTo(onBeforeMount) public init() { this.value++; }}@Setupclass 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++; }}@Setupclass 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';@Setupclass App extends Define { @PassOnTo(onMounted) public onMounted() {}}
Watch
在应用 vue-property-decorator
的 Watch
装璜器时,他会接管一个字符串类型,它不能正确的辨认类实例是否存在这个字段,然而当初 vue-class-setup 能查看你的类型是否正确,如果传入一个类实例不存在的字段,类型将会报错
<script lang="ts">import { Setup, Watch, Context } from 'vue-class-setup';@Setupclass 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';@Setupclass 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