基础知识
1: 面向对象
面向对象编程是 JavaScript 中一种重要的编程范式,它帮助开发者通过类和对象组织代码,提高代码的复用性。
2: 装饰器
装饰器在 JavaScript 中是用于包装 class 或方法的高阶函数
为了统一术语,下面的内容会把装饰器
讲成注解
在 oinone 平台中,无论是字段还是动作,都是通过 ts + vue 来实现的,ts 中是面向对象的写法,所有的属性、方法建议都在对应的 class 中,如果有通用的属性跟方法,可以放在一个公共的 class 中,然后通过继承来实现,这样便于维护。
<!-- FormString.vue -->
<template>
<div>
<p>用户名: {{userName}}</p>
<button @click="updateUser({name: '王五'})">修改用户名</button>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
props: {
userName: {
type: String,
default: ''
},
userInfo: {
type: Object,
default: () => ({})
},
updateUser: {
type: Function,
default: () => () => ({})
}
}
});
</script>
import FormString from './FormString.vue'
@SPI.ClassFactory(
FormFieldWidget.Token({
viewType: [ViewType.Form, ViewType.Search],
ttype: ModelFieldType.String
})
)
export class FormCustomStringFieldWidget extends FormFieldWidget {
public initialize(props) {
super.initialize(props); // 调用父类方法,确保继承的属性和方法正常初始化
this.setComponent(FormString); // 注册 Vue 组件,这样该 Widget 就会渲染 FormString 组件
return this;
}
public otherInfo = {
name:'张三'
}
@Widget.Reactive()
public userInfo = {
name:'李四'
}
@Widget.Reactive()
public get userName() {
return this.userInfo.name
}
@Widget.Method()
public updateUser userName(user) {
this.userInfo = user
}
public updateOtherUser userName(user) {
this.otherUser = user
}
}
这段代码定义了一个 FormCustomStringFieldWidget 类,用于处理表单中 String 类型字段的展示和交互。该类继承自 FormFieldWidget,并使用了多种注解和特性来实现不同功能。下面是对代码的详细讲解。
SPI 讲解
@SPI.ClassFactory()
无论是自定义字段还是动作,或者是自定义 mask、layout,都会用到@SPI.ClassFactory
来注册,@SPI.ClassFactory
是一个注解,它标记了该类是通过工厂模式注册的。
在前端中,所有的注解(装饰器)本质上还是高阶函数,下面是一段伪代码。
const SPI = {
ClassFactory(token) {
return (targetClass) => {
// todo something
};
}
};
class MyClass {}
SPI.ClassFactory(注册条件)(MyClass);
所以我们在使用@SPI.ClassFactory
的时候,就会根据注册条件把对应的 class 注册到对应的工厂中。
注册条件
FormFieldWidget.Token({
viewType: [ViewType.Form, ViewType.Search],
ttype: ModelFieldType.String
});
这段代码调用FormFieldWidget.Token
函数,生成一个 token,这个 token 会作为参数传递给@SPI.ClassFactory
,然后@SPI.ClassFactory
会根据这个 token 来注册对应的 class。
不同类型的 widget 调用的 token 函数不同:
注册条件集合
字段注册条件
- 表单(详情、搜索、画廊)字段:
FormFieldWidget.Token
- 表格字段:
BaseFieldWidget.Token
下面是参数描述和含义:
属性 | 类型 | 说明 |
---|---|---|
viewType |
ViewType | ViewType[] |
当前视图类型,支持单一或多个视图类型。 |
widget |
string | string[] |
组件名称,可以是单个字符串或多个组件名称。 |
ttype |
ModelFieldType | ModelFieldType[] |
字段业务类型,支持单一或多个业务类型。 |
multi |
boolean |
是否多值,true 表示字段支持多个值。 |
model |
string | string[] |
指定模型名称,可以是单一或多个模型。 |
viewName |
string | string[] |
指定视图名称,可以是单一或多个视图名称。 |
name |
string |
指定字段的 name 属性,用于业务逻辑识别。 |
当我们注册 字段 SPI 的时候,对应注册条件基本只会用到
viewType
、ttype
、widget
、这三个属性,其他属性都是可选的。
动作注册条件
- 动作:
ActionWidget.Token
下面是参数描述和含义:
属性 | 类型 | 说明 |
---|---|---|
viewType |
ViewType | ViewType[] |
当前视图类型,支持单一或多个视图类型。 |
actionType |
ActionType | AiewType[] |
当前动作类型,支持单一或多个动作类型。 |
widget |
string | string[] |
组件名称,可以是单个字符串或多个组件名称。 |
target |
ViewActionTarget | ViewActionTarget[] |
打开方式 (视图动作专属) |
viewName |
string | string[] |
指定视图名称,可以是单一或多个视图名称。 |
model |
string | string[] |
指定模型名称,可以是单一或多个模型。 |
name |
string |
指定动作的 name 属性,用于业务逻辑识别。 |
当我们注册 动作 SPI 的时候,对应注册条件基本只会用到
actionType
、model
、name
、这三个属性,其他属性都是可选的。
视图、layout 注册条件
- 动作:
BaseElementWidget.Token
下面是参数描述和含义:
属性 | 类型 | 说明 |
---|---|---|
viewType |
ViewType | ViewType[] |
当前视图类型,支持单一或多个视图类型。 |
inline |
boolean |
当前是否为内嵌视图。 |
widget |
string | string[] |
组件名称,可以是单个字符串或多个组件名称。 |
viewName |
string | string[] |
指定视图名称,可以是单一或多个视图名称。 |
model |
string | string[] |
指定模型名称,可以是单一或多个模型。 |
当我们注册 layout 或者视图 SPI 的时候,对应注册条件基本只会用到
viewType
、widget
、这两个个属性,其他属性都是可选的。
mask 注册条件
- 动作:
MaskWidget.Token
下面是参数描述和含义:
属性 | 类型 | 说明 |
---|---|---|
widget |
string | string[] |
组件名称,可以是单个字符串或多个组件名称。 |
当我们注册 mask SPI 的时候,对应注册条件只会用到
widget
。
路由注册条件
- 动作:
RouterWidget.Token
下面是参数描述和含义:
属性 | 类型 | 说明 |
---|---|---|
widget |
string | string[] |
组件名称,可以是单个字符串或多个组件名称。 |
当我们注册 路由 SPI 的时候,对应注册条件只会用到
widget
。
SPI 覆盖
在开发中,有时我们需要对平台底层已注册的 SPI 进行覆盖。这种情况通常出现在需要修改或扩展某些功能时。下面将介绍如何安全地覆盖 SPI。
以字段为例,加入我们需要覆盖平台默认的字段,这个字段是 Form 表单里面的String
类型,我们希望这个字段可以支持业务扩展的能力,那么我们可以通过如下方式来扩展这个字段:
在 5.0.x 版本中,平台部分源码是开放的,所以我们可以看到平台默认的字段源码,FormStringFieldSingleWidget
就是 Form 表单里面的String
类型对应的 class,它的 SPI 是:
@SPI.ClassFactory(
BaseFieldWidget.Token({
viewType: [ViewType.Form, ViewType.Search],
ttype: ModelFieldType.String
})
)
如果想覆盖它,只需要 SPI 注册条件一致就行。
@SPI.ClassFactory(
FormFieldWidget.Token({
viewType: [ViewType.Form, ViewType.Search],
ttype: ModelFieldType.String
})
)
class ExistingStringFieldWidget extends FormStringFieldSingleWidget {
// 平台已有实现
}
注册 vue 组件
在FormCustomStringFieldWidget
中,我们定义了initialize
函数里面写了一点代码。
import FormString from './FormString.vue'
class FormCustomStringFieldWidget{
public initialize(props) {
super.initialize(props);
this.setComponent(FormString);
return this;
}
}
initialize
函数是固定写法,super.initialize(props)
是用来调用父类的方法,this.setComponent(FormString)
是将 vue 组件注册到当前 widget 中,当自定义的字段不需要重写 vue 组件的时候,那么就不需要重写initialize
函数,只需要继承对应的 class 重写对应的属性、方法即可
当自定义的字段、动作、视图、mask 需要使用自己的 vue 组件时,才需要重写
initialize
函数
ts 与 vue 之间的联动
Widget.Reactive()
在FormCustomStringFieldWidget
中,我们定义了userInfo
和userName
两个属性,通过Widget.Reactive()
注解,将这两个属性变成响应式属性,这样在 vue 组件中中,就可以通过 props 来接受两个属性,otherInfo
属性没有打上任何注解,说明它是一个普通的属性,在对应的 vue 文件里面是获取不到的。
当定义 userName
时,我们为其加上了 get
属性,这会将它转换为一个计算属性
,方便在 Vue 组件中动态更新。
所有可以理解成:
@Widget.Reactive()
public userInfo = {}
等价于 ↓
const userInfo = ref({})
@Widget.Reactive()
public get userName() {
return this.userInfo.name
}
等价于 ↓
const userName = computed(() => userInfo.name)
Widget.Method()
在上述代码中,我们还定义了updateUser
跟updateOtherUser
方法,通过updateUser
上使用了Widget.Method()
注解,将这个方法变成一个响应式方法,这样在 vue 组件中,就可以通过 props 来接受这个方法,调用这个方法,实现数据的更新,updateOtherUser
是一个普通的方法,在 vue 文件中是获取不到的。
最终我们可以得出一个结论,在 ts 中,如果定义的属性跟方法需要在 vue 中获取,那么就要加上Widget.Reactive()
注解,如果是方法,就加上Widget.Method()
注解,如果该属性是一个计算属性,就加上get
属性。
伪代码实现:
import { ref, computed } from 'vue';
const Widget = {
Reactive(target, key, description) {
if (description.get) {
target[key] = computed(description.get);
} else {
target[key] = ref(target[key]);
}
},
Method(target, key) {
target[key] = ref(target[key]);
}
};
完整案例:用户信息管理
场景:我们将创建一个用户信息管理的小模块,允许用户查看和修改他们的个人信息,包括姓名和邮箱地址。我们将用 Vue 组件展示这些信息,并使用 TS 中的类来管理逻辑。此案例将展示如何使用 @Widget.Reactive() 和 @Widget.Method() 注解来确保数据和方法的响应式更新。
第一步:Vue 组件定义
我们首先定义一个简单的 Vue 组件,用于显示和更新用户信息。
<!-- UserInfo.vue -->
<template>
<div>
<p>姓名: {{ userName }}</p>
<p>邮箱: {{ userEmail }}</p>
<button @click="updateUserInfo({ name: '李四', email: 'li.si@example.com' })">修改用户信息</button>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
props: {
userName: {
type: String,
default: ''
},
userEmail: {
type: String,
default: ''
},
updateUserInfo: {
type: Function,
default: () => () => ({})
}
}
});
</script>
第二步:TypeScript 类定义
我们在 TypeScript 中定义一个类 FormStringUserFieldWidget,用于管理用户信息。我们使用 @Widget.Reactive() 和 @Widget.Method() 来确保数据的响应式更新和方法的可用性。
import UserInfo from './UserInfo.vue';
@SPI.ClassFactory(
FormFieldWidget.Token({
viewType: [ViewType.Form],
ttype: ModelFieldType.String,
widget: 'FormStringUserFieldWidget'
})
)
export class FormStringUserFieldWidget extends FormFieldWidget {
// 注册 Vue 组件
public initialize(props: any) {
super.initialize(props);
this.setComponent(UserInfo); // 将 UserInfo.vue 组件注册到当前 Widget
return this;
}
// 用户基础信息
@Widget.Reactive()
public userInfo = {
name: '张三',
email: 'zhang.san@example.com'
};
// 返回用户姓名的计算属性
@Widget.Reactive()
public get userName() {
return this.userInfo.name;
}
// 返回用户邮箱的计算属性
@Widget.Reactive()
public get userEmail() {
return this.userInfo.email;
}
// 更新用户信息的方法
@Widget.Method()
public updateUserInfo(newInfo: { name: string; email: string }) {
this.userInfo = { ...this.userInfo, ...newInfo };
}
}
第三步:案例解析
- initialize 方法:
- 我们使用 initialize 方法来注册 Vue 组件 UserInfo.vue。当这个类被实例化时,Vue 组件会被渲染,并且与类中的响应式数据和方法建立连接。
- userInfo 属性:
- 这是用户的基本信息对象,包含 name 和 email 两个字段。我们使用 @Widget.Reactive() 注解将 userInfo 变成响应式数据,这样在 Vue 组件中可以随时获取和更新。
- userName 和 userEmail 计算属性:
- 我们使用 get 关键字为 userName 和 userEmail 定义了计算属性,并使用 @Widget.Reactive() 注解,这样 Vue 组件可以根据 userInfo 的变化自动更新显示内容。
- updateUserInfo 方法:
- 这是用于更新用户信息的响应式方法。通过 @Widget.Method() 注解,我们确保 Vue 组件能够调用此方法。点击 Vue 组件中的按钮后,会触发 updateUserInfo 方法更新用户的姓名和邮箱。
- Vue 组件
- 我们通过 props 接收了从 TypeScript 类传递过来的 userName、userEmail 和 updateUserInfo 方法。点击按钮后,updateUserInfo 方法会被调用,用户信息将被更新。
通过这个完整的案例,我们展示了如何将 TypeScript 与 Vue 结合,通过 @Widget.Reactive() 和 @Widget.Method() 实现数据的响应式联动和方法调用。
Oinone社区 作者:汤乾华原创文章,如若转载,请注明出处:https://doc.oinone.top/frontend/17632.html
访问Oinone官网:https://www.oinone.top获取数式Oinone低代码应用平台体验