# vue3-class-component
[](https://npmjs.com/package/@qubit-ltd/vue3-class-component)
[](https://www.apache.org/licenses/LICENSE-2.0)
[](README.md)
[](https://dl.circleci.com/status-badge/redirect/gh/qubit-ltd/vue3-class-component/tree/master)
[](https://coveralls.io/github/qubit-ltd/vue3-class-component?branch=master)
*最后更新:2024年5月*
这个库允许您使用类式语法创建您的 [Vue] 组件。它从 [vue-class-component] 得到了很多灵感,但有一些显著的区别:
- 它支持 [Vue] v3.x.x(当前版本为 v3.5.13)。
- 与 [vue-facing-decorator] 不同,它使用纯 JavaScript 而非 TypeScript 编写,不再需要配置使用 TypeScript。
- 它采用了最新的(截止到2023年5月) [JavaScript 装饰器第3阶段提案] 和
[JavaScript 装饰器元数据第3阶段提案]。
- 它提供了用于类式 Vue 组件的常用装饰器,如 @Prop、@Watch、@Provide 和 @Inject(更多详情
请参阅[预定义装饰器](#predefined-decorators)部分)。简而言之,它结合了 [vue-class-component] 和
[vue-property-decorator] 的功能。
- 同时提供 UMD 和 ESM 打包格式,支持[webpack]和[vite]。更多详细信息,请参阅[配置](#configuration)部分。
- 经过详细单元测试,代码覆盖率达到了100%。
- 代码全面重写,装饰器功能经过重新设计,更加合理。
## 主要特性
- **完整支持 Vue 3**:完全兼容 Vue 3 的 Composition API,同时提供优雅的基于类的语法。
- **无缝集成**:同时支持 webpack 和 Vite 构建系统。
- **强大的类型推断**:自动推断 props 和默认值的类型。
- **全面的装饰器集**:包含所有 Vue 功能的完整装饰器集,包括属性绑定、模型绑定、侦听器、提供/注入等。
- **自定义装饰器 API**:通过简单的 API 创建您自己的自定义装饰器。
- **无需 TypeScript**:享受基于类的组件的好处,无需 TypeScript 配置开销。
- **经过全面测试**:完整的测试覆盖确保稳定性和可靠性。
## 快速入门
下面是使用本库的一个简单 Vue 3 组件示例:
```vue
{{ greeting }}
计数: {{ count }}
```
## 目录
- [安装方式](#installation)
- [配置方式](#configuration)
- [使用示例](#usage-example)
- [支持的选项](#supported-options)
- [预定义装饰器](#predefined-decorators)
- [@Prop 装饰器](#Prop)
- [@VModel 装饰器](#VModel)
- [@Watch 装饰器](#Watch)
- [@Provide 装饰器](#Provide)
- [@Inject 装饰器](#Inject)
- [@Raw 装饰器](#Raw)
- [自定义装饰器](#customize-decorators)
- [贡献](#contributing)
- [许可](#license)
## 安装方式
```bash
yarn add @qubit-ltd/vue3-class-component @qubit-ltd/typeinfo @qubit-ltd/clone
```
or
```bash
npm install @qubit-ltd/vue3-class-component @qubit-ltd/typeinfo @qubit-ltd/clone
```
注意:这个库依赖于 [@qubit-ltd/typeinfo] 和 [@qubit-ltd/clone] 库,因此您必须同时安装它们。
## 配置方式
这个库使用了最新的(截止到2023年5月)[JavaScript 装饰器第3阶段提案] 和
[JavaScript 装饰器元数据第3阶段提案],因此您必须配置 [Babel],
使用 [@babel/plugin-transform-class-properties] 和
[@babel/plugin-proposal-decorators] 插件。
**注意:** 为了支持 [JavaScript 装饰器元数据第3阶段提案],
插件 [@babel/plugin-proposal-decorators] 的版本号必须至少为 `7.26.0`。
### 使用 [webpack] 打包
1. 安装需要的依赖:
```bash
yarn add @qubit-ltd/vue3-class-component @qubit-ltd/typeinfo @qubit-ltd/clone
yarn add --dev @babel/core @babel/runtime @babel/preset-env
yarn add --dev @babel/plugin-proposal-decorators @babel/plugin-transform-class-properties @babel/plugin-transform-runtime
```
2. 配置 [Babel],使用 [@babel/plugin-transform-class-properties] 和
[@babel/plugin-proposal-decorators] 插件。一个可能的 [Babel] 配置文件 `babelrc.json` 如下:
```json
{
"presets": [
"@babel/preset-env"
],
"plugins": [
"@babel/plugin-transform-runtime",
["@babel/plugin-proposal-decorators", { "version": "2023-11" }],
"@babel/plugin-transform-class-properties"
]
}
```
详细配置过程可以参考:
- 使用 [vue-cli] 和 [webpack] 创建的演示项目:[vue3-class-component-demo-webpack]
### 使用 [vite] 打包
1. 安装需要的依赖:
```bash
yarn add @qubit-ltd/vue3-class-component @qubit-ltd/typeinfo @qubit-ltd/clone
yarn add --dev @babel/core @babel/runtime @babel/preset-env
yarn add --dev @babel/plugin-proposal-decorators @babel/plugin-transform-class-properties @babel/plugin-transform-runtime
```
2. 配置 [Babel],使用 [@babel/plugin-transform-class-properties] 和
[@babel/plugin-proposal-decorators] 插件。一个可能的 [Babel] 配置文件 `babelrc.json` 如下:
```json
{
"presets": [
["@babel/preset-env", { "modules": false }]
],
"plugins": [
"@babel/plugin-transform-runtime",
["@babel/plugin-proposal-decorators", { "version": "2023-11" }],
"@babel/plugin-transform-class-properties"
]
}
```
**注意:** 使用 [vite] 打包需要将 `@babel/preset-env` 的参数 `modules` 设置为 `false`。
3. 配置 [vite],修改 `vite.config.js` 文件,使其支持 [Babel]。一个可能的 `vite.config.js` 文件如下:
```javascript
import { fileURLToPath, URL } from 'node:url';
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import * as babel from '@babel/core';
// A very simple Vite plugin support babel transpilation
const babelPlugin = {
name: 'plugin-babel',
transform: (src, id) => {
if (/\.(jsx?|vue)$/.test(id)) { // the pattern of the file to handle
return babel.transform(src, {
filename: id,
babelrc: true,
});
}
},
};
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue({
script: {
babelParserPlugins: ['decorators'], // must enable decorators support
},
}),
babelPlugin, // must after the vue plugin
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
});
```
**注意:** 在上面配置文件中我们实现了一个简单的 [Vite] 插件用于将 [vite-plugin-vue]
插件处理过的代码通过 [babel] 转译。虽然有个 [vite-plugin-babel] 插件声称可以让 [vite]
支持 [babel],但我们发现它无法正确处理 [vue] 的 SFC 格式 (`*.vue`格式文件)。仔细研究
它的源码后,我们发现要实现正确的转译,必须在 [vite-plugin-vue] 插件处理过源码之后再使用
[babel] 进行转译,因此只需上面非常简单的插件函数即可实现我们需要的功能。作为一个替代选择,
你可以使用 [我们的 vite-plugin-babel 插件],下面是一个配置示例:
```js
import { fileURLToPath, URL } from 'node:url';
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import babel from '@qubit-ltd/vite-plugin-babel';
export default defineConfig({
plugins: [
vue({
script: {
babelParserPlugins: ['decorators'], // must enable decorators support
},
}),
babel(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
});
```
详细配置过程可以参考:
- 使用 [create-vue] 和 [vite] 创建的演示项目:[vue3-class-component-demo-vite]
## 使用示例
```vue
{{ message }}
{{ computedMessage }}
{{ value }}
```
上述代码等效于:
```vue
{{ message }}
{{ computedMessage }}
{{ value }}
```
## 支持的选项
`@Component` 装饰器可以与选项参数一起使用,这些选项参数将传递给生成的 Vue 组件的选项。例如:
```js
@Component({
name: 'Hello', // override the name of the class
components: {
PhoneLink,
},
filters: {
capitalize: (s) => s.toUpperCase(),
},
})
class HelloPage {
message = 'hello';
value = 0;
newMessage = '';
mounted() {
this.value = this.$route.params.value;
}
get computedMessage() {
return this.message + '!';
}
setMessage(s) {
this.message = s;
}
}
export default toVue(HelloPage); // don't forget calling `toVue`
```
上述代码等效于:
```js
export default {
name: 'Hello',
components: {
PhoneLink,
},
filters: {
capitalize: (s) => s.toUpperCase(),
},
data() {
return {
message: 'hello',
value: 0,
newMessage: '',
};
},
mounted() {
this.value = this.$route.params.value;
},
computed: {
computedMessage() {
return this.message + '!';
},
},
methods: {
setMessage(s) {
this.message = s;
},
},
};
```
下表列出了Vue选项API中的所有关键词以及它们是否受 `@Component` 装饰器参数的支持:
| 类别 | 选项 | 是否支持 | 描述 |
|-------------|-------------------|-------|------------------------------------------------|
| State | `data` | 否 | 组件的响应式状态应该定义为类字段。 |
| State | `props` | 否 | 组件的属性应该定义为类字段,并使用 `@Prop` 装饰器标记。 |
| State | `computed` | 否 | 计算属性应该定义为类 getter。 |
| State | `methods` | 否 | 组件的方法应该定义为类方法。 |
| State | `watch` | 否 | 观察者应该定义为类方法,并使用 `@Watch` 装饰器标记。 |
| State | `emits` | 是 | 由 Vue 组件发出的自定义事件可以在 `@Component` 的选项中声明。 |
| State | `expose` | 是 | 可公开的公共属性可以在 `@Component` 的选项中声明。 |
| Rendering | `template` | 是 | 字符串模板可以在 @Component 的选项中声明。 |
| Rendering | `render` | 否 | 渲染函数应该定义为类方法。 |
| Rendering | `compilerOptions` | 是 | 字符串模板的编译器选项可以在 @Component 的选项中声明。 |
| Rendering | `slot` | 是 | Vue 组件的插槽可以在 @Component 的选项中声明。 |
| Lifecycle | `beforeCreate` | 否 | beforeCreate 钩子应该定义为类方法。 |
| Lifecycle | `created` | 否 | `created` 钩子应该定义为类方法。 |
| Lifecycle | `beforeMount` | 否 | `beforeMount` 钩子应该定义为类方法。 |
| Lifecycle | `mounted` | 否 | `mounted` 钩子应该定义为类方法。 |
| Lifecycle | `beforeUpdate` | 否 | `beforeUpdate` 钩子应该定义为类方法。 |
| Lifecycle | `updated` | 否 | `updated` 钩子应该定义为类方法。 |
| Lifecycle | `beforeUnmount` | 否 | `beforeUnmount` 钩子应该定义为类方法。 |
| Lifecycle | `unmounted` | 否 | `unmounted` 钩子应该定义为类方法。 |
| Lifecycle | `errorCaptured` | 否 | `errorCaptured` 钩子应该定义为类方法。 |
| Lifecycle | `renderTracked` | 否 | `renderTracked` 钩子应该定义为类方法。 |
| Lifecycle | `renderTriggered` | 否 | `renderTriggered` 钩子应该定义为类方法。 |
| Lifecycle | `activated` | 否 | `activated` 钩子应该定义为类方法。 |
| Lifecycle | `deactivated` | 否 | `deactivated` 钩子应该定义为类方法。 |
| Lifecycle | `serverPrefetch` | 否 | `serverPrefetch` 钩子应该定义为类方法。 |
| Composition | `provide` | 否 | `provide` 属性应该定义为类字段,并使用 `@Provide` 装饰器标记。 |
| Composition | `inject` | 否 | `inject` 属性应该定义为类字段,并使用 `@Inject` 装饰器标记。 |
| Composition | `mixins` | 是 | 可以在 `@Component` 的选项中声明混入的对象数组。 |
| Composition | `extends` | 是 | 可以在 `@Component` 的选项中声明要扩展的基础 Vue 组件。 |
| Misc | `name` | 是 | Vue 组件的名称可以在 `@Component` 的选项中声明;否则将使用装饰的类的类名。 |
| Misc | `inheritAttrs` | 是 | inheritAttrs 可以在 `@Component` 的选项中声明。 |
| Misc | `components` | 是 | Vue 组件的注册组件可以在 `@Component` 的选项中声明。 |
| Misc | `directives` | 是 | Vue 组件的注册指令可以在 `@Component` 的选项中声明。 |
## 预定义装饰器
这个库为类式 Vue 组件提供了以下常用装饰器:
- [`@Prop` 装饰器](#Prop)
- [`@VModel` 装饰器](#VModel)
- [`@Watch` 装饰器](#Watch)
- [`@Provid` 装饰器](#Provide)
- [`@Inject` 装饰器](#Inject)
### @Prop 装饰器
`@Prop` 装饰器应用在类字段上,用于声明 Vue 组件的 props。
例如:
```js
@Component
class HelloPage {
// 如果新属性定义时有默认值,则无需指定其类型和默认值,系统会自动推断其类型
@Prop
message = 'hello';
@Prop({ type: Number, validator: (v) => (v >= 0) })
value;
// 非基本数据类型的默认值 **无需** 用工厂函数包装
@Prop
person = {
id: 1,
name: 'John',
age: 32,
gender: 'MALE',
};
// 多个可能的属性类型,可以表示为一个构造器数组
@Prop({ type: [Boolean, String] })
lazy;
// 如果装饰器的参数是一个函数,它将被认为是新属性的类型
@Prop(Number)
value2;
// 如果装饰器的参数是一个函数数组,它将被认为是新属性的可能的类型
@Prop([Boolean, String, Number])
value3;
// 如果属性值可空,则其类型数组中可以包含一个`null`
@Prop([String, null])
value4;
}
export default toVue(HelloPage);
```
上述代码等效于:
```js
export default {
name: 'HelloPage',
props: {
message: {
type: String,
default: 'hello',
},
value: {
type: Number,
required: true,
validator: (v) => {
return v >= 0
},
},
person: {
type: Object,
required: false,
default: () => ({
id: 1,
name: 'John',
age: 32,
gender: 'MALE',
}),
},
lazy: {
type: [Boolean, String],
required: true,
},
value2: {
type: Number,
required: true,
},
value3: {
type: [Boolean, String, Number],
required: true,
},
value4: {
type: [String, null],
required: true,
},
},
};
```
`@Prop` 装饰器可以有一个可选参数。`@Prop` 装饰器的参数是一个包含以下选项的对象:
| 选项 | 类型 | 默认值 | 描述 |
|-------------|------------|-------------|--------------------|
| `type` | `Function` | `undefined` | Prop 的数据类型,应为构造函数。 |
| `required` | `Boolean` | `false` | 指示是否需要传递该 prop。 |
| `default` | `any` | `undefined` | 指定 prop 的默认值。 |
| `validator` | `Function` | `undefined` | 用于 prop 的自定义验证函数。 |
- `type`: 此选项定义了 prop 期望的数据类型,可以是以下之一:`String`、`Number`、
`Boolean`、`Array`、`Object`、`Date`、`Function`、`Symbol`、自定义的类、自定义的构造
函数,或这些类型的数组。在开发模式下,Vue 会验证 prop 的值是否与声明的类型匹配,如果不匹配,
将发出警告。有关更多详情,请参阅[属性校验]。
如果一个 prop允许多种可能的类型,可以在这个选项中指定一个构造函数数组。例如:
`{ type: [Boolean, String] }`。
请注意,具有 `Boolean` 类型的 prop 在开发和生产模式下都会影响其值的类型转换行为。有关更多
详情,请参阅[布尔型强制转换]。
如果未指定此选项,则库将从装饰的类字段的初始值中推断出类型。
- `default`: 使用此选项为 prop 指定默认值,当它未由父组件传递或具有未定义的值时将会生效。对
象或数组类型的默认值必须使用工厂函数返回。工厂函数还接收原始的 prop 对象作为参数。如果未指定
此选项,则库将自动从装饰的类字段的初始值中推断默认值。
值得注意的是,[Vue] 库要求 prop 的非原始类型默认值必须使用工厂函数包装,但我们的库会自动处
理此问题。因此,**在声明 prop 时,不需要使用工厂函数包装非原始类型的默认值。**
- `required`: 使用此选项来指定是否必须传递 prop。在非生产环境中,如果此值为真且未提供 prop,
则会生成控制台警告。
如果未指定此选项,则库将自动推断装饰的类字段的初始值是否已提供,以确定是否需要 prop。
- `validator`: 此选项允许您定义一个自定义验证函数,该函数以 prop 值作为其唯一参数。在开发模
式下,如果此函数返回假值(即验证失败),则会生成控制台警告。
如果 `@Prop` 装饰器的参数是一个函数,或者一个函数组成的数组,它将被认为是为新 prop 指定的类型。
例如:
```js
@Component
class HelloPage {
@Prop(Number)
value1;
@Prop([Boolean, String, Number])
value2;
}
```
如果属性定义时给出了默认值,则无需再指定其类型和默认值,系统会自动推断。例如:
```js
@Component
class HelloPage {
@Prop
message = '';
@Prop
value = 0;
}
```
### @VModel 装饰器
`@VModel` 装饰器与 `@Prop` 装饰器类似,不同之处在于它支持 `v-model` 绑定。
有关更多详细信息,请参见[组件 v-model]。
例如:
```vue
```
等同于:
```vue
```
上述自定义组件中的`@VModel`属性可按如下方式使用:
```vue
```
**注意:**
- 默认的 `v-model` 绑定属性名 `'modelValue'` 不应用作类字段名或类方法名。
- 为了简化实现,这个库**不**支持多个 `v-model` 绑定。此外,它也**不**支持 `v-model` 修饰符,
**也不**允许更改默认的 `v-model` 绑定属性名。
类似于 `@Prop` 装饰器,`@VModel` 装饰器也可以接受一个可选参数。这个用于 `@VModel`
装饰器的参数是一个包含附加选项的对象。`@VModel` 可用的选项与 `@Prop` 支持的选项完全相同。
有关更多详细信息,请参见 [@Prop 装饰器](#Prop)。
### @Watch 装饰器
`@Watch` 装饰器应用在类方法上,用于声明 Vue 组件的观察者。
例如:
```js
@Component
class HelloPage {
value = 123;
person = {
id: 1,
name: 'John',
age: 32,
gender: 'MALE',
};
@Watch('value')
onValueChanged(val, oldVal) {
console.log(`The value is changed from ${oldVal} to ${val}.`);
}
@Watch('person', { deep: true })
onPersonChanged(val, oldVal) {
console.log(`The person is changed from ${oldVal} to ${val}.`);
}
}
export default toVue(HelloPage);
```
上述代码等效于:
```js
export default {
name: 'HelloPage',
data() {
return {
value: 123,
};
},
watch: {
value(val, oldVal) {
console.log(`The value is changed from ${oldVal} to ${val}.`);
},
person: {
deep: true,
handler(val, oldVal) {
console.log(`The person is changed from ${oldVal} to ${val}.`);
},
},
},
};
```
`@Watch` 装饰器可以带有一个或两个参数。`@Watch` 装饰器的第一个参数是被监视的状态或属性的路径,
第二个可选参数是一个包含以下选项的对象:
| 选项 | 类型 | 默认值 | 描述 |
|-------------|-----------|---------|------------------------------------------------|
| `deep` | `Boolean` | `false` | 观察者是否应深度遍历源对象(如果它是对象或数组)。 |
| `immediate` | `Boolean` | `false` | 观察者是否应在创建后立即调用。 |
| `flush` | `String` | `'pre'` | 观察者的刷新时机。可以是 `'pre'`、`'post'` 或 `'sync'` 中的一个。 |
- `deep`: 如果源对象是对象或数组,强制深度遍历,以便在发生深层变化时触发回调。
请参阅[深度观察者]。
- `immediate`: 在观察者创建时立即触发回调。第一次调用时旧值将为 `undefined`。
请参阅[急切观察者]。
- `flush`: 调整回调的刷新时机。可以是 `'pre'`、`'post'` 或 `'sync'` 中的一个。
请参阅[回调刷新时机]和[watchEffect()]。
**注意:** 与 [vue-property-decorator] 中的 `@Watch` 装饰器不同,此库中的 `@Watch`
装饰器不支持使用多个观察处理程序同时监视相同的状态或属性。因为这不是常见用例,所以我们决定简
化`@Watch`装饰器的实现。
### @Provide 装饰器
`@Provide` 装饰器应用在类字段上,用于声明可以由子组件注入的提供的值。
例如:
```js
const myInjectedKey = Symbol('myInjectedKey');
@Component
class AncestorComponent {
@Provide
message = 'hello';
@Provide({key: myInjectedKey, reactive: true})
@Prop
value = 123;
@Provide({ reactive: true })
person = {
id: 1,
name: 'John',
age: 32,
gender: 'MALE',
};
}
export default toVue(AncestorComponent);
```
上述代码等效于:
```js
import { computed } from 'vue'
export default {
name: 'AncestorComponent',
props: {
value: {
type: Number,
default: 123,
required: false,
},
},
data() {
return {
message: 'hello',
};
},
provide() {
return {
message: this.message, // non-reactive
[myInjectedKey]: computed(() => this.value), // reactive
person: computed(() => this.person), // reactive
};
},
};
```
`@Provide` 和 `@Inject` 装饰器一起使用,允许祖先组件作为所有后代组件的依赖注入器,无论组件
层次结构有多深,只要它们在同一父级链中。有关详细信息,请参阅[Provide / Inject]。
`@Provide` 装饰器可以带有一个可选参数。可选参数是一个包含以下选项的对象:
| 选项 | 类型 | 默认值 | 描述 |
|------------|--------------------|-------------|-------------|
| `key` | `String \| Symbol` | `undefined` | 提供值的键。 |
| `reactive` | `Boolean` | `false` | 提供值是否是响应式的。 |
- `key`: 子组件使用键来定位要注入的正确值。键可以是字符串或符号。有关更多详情,请参阅[使用符号键]。
如果未指定此选项,则将使用由 `@Provide` 装饰器装饰的字段的名称作为键。
- `reactive`: 指示提供的值是否是响应式的。默认情况下,提供的值不是响应式的,即在祖先组件中更
改提供的值不会影响后代组件中注入的值。如果将此选项设置为 `true`,则提供的值将变为响应式。
有关更多详情,请参阅[使用响应式]。
**注意:** [vue-property-decorator] 提供了 `@Provide` 和 `@ProvideReactive` 装饰器,
分别用于声明非响应式和响应式的提供的值。但此库通过提供一个带有可选 `reactive` 选项的
`@Provide` 装饰器来简化实现。由于提供的值通常是非响应式的,我们决定将 `reactive` 选项的默认
值设置为 `false`。
### @Inject 装饰器
`@Inject` 装饰器应用在类字段上,用于声明被注入的值。
例如:
```js
@Component
class DescendantComponent {
@Inject
message;
@Inject({from: myInjectedKey, default: 0})
injectedValue;
// non-primitive default value DO NOT need to be wrapped by a factory function
@Inject({ default: {id: 0, name: 'unknown'} })
person;
}
export default toVue(DescendantComponent);
```
上述代码等效于:
```js
export default {
name: 'DescendantComponent',
inject: {
message: { // non-reactive
from: 'message',
default: undefined,
},
injectedValue: { // reactive, since the provided `myInjectedKey` is reactive
from: myInjectedKey,
default: 0,
},
person: { // reactive, since the provided `person` is reactive
from: 'person',
default: () => ({id: 0, name: 'unknown'}),
},
},
};
```
`@Provide` 和 `@Inject` 装饰器一起使用,允许祖先组件作为所有后代组件的依赖注入器,无论组件
层次结构有多深,只要它们在同一父级链中。有关详细信息,请参阅[Provide / Inject]。
`@Inject` 装饰器可以带有一个可选参数。可选参数是一个包含以下选项的对象:
| 选项 | 类型 | 默认值 | 描述 |
|-----------|--------------------|-------------|-------------|
| `from` | `String \| Symbol` | `undefined` | 要注入的源提供值的键。 |
| `default` | `any` | `undefined` | 被注入值的默认值。 |
- `from`: 此选项的值指定要注入的提供值的键。键可以是字符串或符号。有关更多详情,请参阅[使用符号键]。
如果未指定此选项,则将使用由 `@Inject` 装饰器装饰的字段的名称作为键。
- `default`: 被注入属性的默认值。请注意,类似于 `@Prop` 装饰器的 `default` 选项,此库将自
动将非原始类型的默认值转换为工厂函数。
**注意:** 如果提供的值是非响应式的,则相应的注入值也是非响应式的。如果提供的值是响应式的,
则相应的注入值也是响应式的。有关更多详情,请参阅[使用响应式]。
**注意:** [vue-property-decorator] 提供了 `@Inject` 和 `@InjectReactive` 装饰器,
分别用于声明非响应式和响应式的注入值。但此库通过提供只有一个 `@Inject` 装饰器来简化实现,
并且注入值的响应性由提供值的响应性决定。
### @Raw 装饰器
`@Raw` 装饰器标记在类字段上,用于声明该字段应被视为原始值,这意味着它不应被包装在响应式代理中。
当你想要将非响应式属性注入到 Vue 组件中时,这个装饰器非常有用。这个装饰器的作用有点类似于
composition API 中的 `markRaw()` 函数。
例如
```js
@Component
class HelloPage {
message = 'hello';
@Raw
rawValue = 123;
@Raw
rawObject = {
id: 1,
name: 'John',
age: 32,
gender: 'MALE',
};
@Raw
client = new Client({
url: '/graphql',
});
created() {
console.log(this.rawValue);
console.log(this.rawObject);
this.client.fetch();
}
}
```
上述代码等效于:
```js
export default {
name: 'HelloPage',
data() {
return {
message: 'hello',
};
},
created() {
console.log(this.rawValue);
console.log(this.rawObject);
this.client.fetch();
},
mixins: [{
created() {
this.rawValue = 123;
this.rawObject = {
id: 1,
name: 'John',
age: 32,
gender: 'MALE',
};
this.client = new Client({
url: '/graphql',
});
},
}],
};
```
注意,直接在`data()`中返回一个被`markRaw()`函数所标记的对象属性,是无效的。因为`markRaw()`函数
只能在 compositional API 的 `setup()` 函数中使用。因此,我们在`mixins`中注入一个`created()`
生命周期钩子函数来初始化这些非响应式的属性。
## 自定义装饰器
此库提供了一个 `createDecorator()` 函数,用于创建自定义装饰器。该函数接受一个回调函数作为参
数,并返回一个装饰器函数。回调函数将使用以下参数进行调用:
- `Class`:被装饰类的构造函数。
- `instance`:被装饰类的默认构造实例。此默认实例可用于获取被装饰类的所有实例字段。
- `target`:被装饰的目标值,可以是类方法、getter 或 setter。请注意,如果被装饰的目标是类字
段,此参数将始终为 `undefined`。
- `context`:包含有关被装饰目标的信息的上下文对象,如 [JavaScript 装饰器第3阶段提案] 和
[JavaScript 装饰器元数据第3阶段提案] 中所述。
- `options`:Vue 组件选项对象。对此对象的更改将影响提供的组件。该对象包含了 Vue 组件选项对象应具备的所有属性,
还额外包含一个名为 `fields` 的属性,它是一个包含了 Vue 组件的所有响应式状态的对象; 也就是说,
它是 Vue 组件的 `data()` 函数返回的对象。修改 `options` 的 `fields` 属性允许您更改 Vue
组件的 `data()` 函数返回的响应式状态。
此库将调用回调函数,以便给它一个机会来修改 Vue 组件选项。回调函数的返回值将被忽略。
`createDecorator()` 函数将返回一个装饰器函数,该函数接受以下两个参数:
- `target`:被装饰的目标值,可以是类方法、类字段、getter 或 setter。如果被装饰的目标是类字
段,此参数将始终为 `undefined`。
- `context`:包含有关被装饰目标的信息的上下文对象,如 [JavaScript 装饰器第3阶段提案] 和
[JavaScript 装饰器元数据第3阶段提案] 中所述。
以下是一个示例用法:
```js
const Log = createDecorator((Class, instance, target, context, options) => {
if (context?.kind !== 'method') {
throw new Error('The @Log decorator can only be used to decorate a class method.');
}
const methodName = context.name;
const originalMethod = options.methods[methodName];
options.methods[methodName] = function (...args) {
console.log(`${Class.name}.${methodName}: ${args.join(', ')}`);
return originalMethod.apply(this, args);
};
});
```
上面的示例演示了如何创建一个 `@Log` 装饰器,该装饰器可用于记录类方法的参数。例如:
```js
@Component
class HelloPage {
message = 'hello';
value = 0;
newMessage = '';
@Log
mounted() {
this.value = this.$route.params.value;
}
get computedMessage() {
return this.message + '!';
}
@Log
setMessage(s) {
this.message = s;
}
}
export default toVue(HelloPage);
```
**注意:** 上述的 @Log 装饰器不能应用于组件类的 getter 或 setter。
## 贡献
如果你发现任何问题或有改进建议,欢迎提交 issue 或者 PR 到本项目的 [GitHub 仓库]。
## 许可
[vue3-class-component] 采用 Apache 2.0 许可证。详细信息请查阅 [LICENSE](LICENSE) 文件。
[Vue]: https://vuejs.org/
[vue-cli]: https://cli.vuejs.org/
[webpack]: https://webpack.js.org/
[create-vue]: https://github.com/vuejs/create-vue
[vite]: https://vitejs.dev/
[vue-class-component]: https://github.com/vuejs/vue-class-component
[vue-property-decorator]: https://github.com/kaorun343/vue-property-decorator
[vue-facing-decorator]: https://github.com/facing-dev/vue-facing-decorator
[Babel]: https://babeljs.io/
[@babel/plugin-transform-class-properties]: https://babeljs.io/docs/babel-plugin-transform-class-properties
[@babel/plugin-proposal-decorators]: https://babeljs.io/docs/babel-plugin-proposal-decorators
[JavaScript 装饰器第3阶段提案]: https://github.com/tc39/proposal-decorators
[JavaScript 装饰器元数据第3阶段提案]: https://github.com/tc39/proposal-decorator-metadata
[属性校验]: https://vuejs.org/guide/components/props.html#prop-validation
[布尔型强制转换]: https://vuejs.org/guide/components/props.html#boolean-casting
[深度观察者]: https://vuejs.org/guide/essentials/watchers#deep-watchers
[急切观察者]: https://vuejs.org/guide/essentials/watchers.html#eager-watchers
[回调刷新时机]: https://vuejs.org/guide/essentials/watchers#callback-flush-timing
[watchEffect()]: https://vuejs.org/api/reactivity-core#watcheffect
[Provide / Inject]: https://vuejs.org/guide/components/provide-inject.html
[组件 v-model]: https://vuejs.org/guide/components/v-model.html#component-v-model
[使用符号键]: https://vuejs.org/guide/components/provide-inject.html#working-with-symbol-keys
[使用响应式]: https://vuejs.org/guide/components/provide-inject#working-with-reactivity
[vue3-class-component]: https://npmjs.com/package/@qubit-ltd/vue3-class-component
[vue3-class-component-demo-webpack]: https://github.com/qubit-ltd/vue3-class-component-demo-webpack
[vue3-class-component-demo-vite]: https://github.com/qubit-ltd/vue3-class-component-demo-vite
[vite-plugin-vue]: https://www.npmjs.com/package/@vitejs/plugin-vue
[vite-plugin-babel]: https://www.npmjs.com/package/vite-plugin-babel
[我们的 vite-plugin-babel 插件]: https://npmjs.com/package/@qubit-ltd/vite-plugin-babel
[GitHub 仓库]: https://github.com/qubit-ltd/vue3-class-component
[@qubit-ltd/typeinfo]: https://npmjs.com/package/@qubit-ltd/typeinfo
[@qubit-ltd/clone]: https://npmjs.com/package/@qubit-ltd/clone