各位网友大家好,今天给大家推荐我刚发布的 Vue 3 开发库 ------ vue-clazz-decorator。
名字里的 "clazz" 是 class 的谐音梗,为什么不直接叫 vue-class-decorator 呢?很简单,因为这个已经有人叫了。
这个库就干一件事:让你用 类 + 装饰器 写 Vue 3 组件,同时把模板和逻辑彻底解开。
这个库的主要特色
- 通过类和装饰器来写 Vue 组件
- 方便地复用模板和逻辑
- 保证自定义类型的纯净
- 支持多种装饰器编译方式
创建组件
这个库的核心思想就是 【组件 = 模板 + 业务逻辑 】。createComponent 这个函数就是围绕这个思想设计的,在避开 Vue 3 不能用 class 组件这个限制的同时,顺便把模板和逻辑的复用也安排得明明白白。
先看看 Vue 3 里同类库的写法,往往长这样
tsx
class HelloPage {
render() {
return xxxxxx;
}
}
export default toVue(HelloPage);
能用,但总觉得拧巴:
render方法硬生生塞进业务类里,UI 和逻辑搅在一起,类本身就不纯粹了;- 外面还得套一层转换函数,多余,但又不能没有。
我换了个更干脆的思路:模板归模板,逻辑归逻辑,两者彻底分开,最后拼一下就是组件。
tsx
@ViewModel
export class CounterViewModel {
@State
public count = 0;
public increment() {
this.count++;
}
}
export function CounterView(props: CounterViewModel) {
return <div>
<p>Count: {props.count}</p>
<button onClick={props.increment}>+1</button>
</div>;
}
export const Counter = createComponent(CounterView, CounterViewModel);
业务逻辑全在 ViewModel 里,就是个干干净净的普通类;模板就是个纯渲染函数,不碰任何业务实现。最后 createComponent 轻轻一粘,就是个标准 Vue 组件。
这么拆开之后,很多事情就顺了:
- 业务类想单测直接实例化测,不用挂载组件、不用模拟 DOM;
- 同一份逻辑可以配多套模板,同一份模板也能套不同逻辑;
优雅的逻辑复用
复用逻辑,直接 use() 把另一个 ViewModel 组合进来,天然带命名空间,互不干扰。
ts
@ViewModel
export class FooViewModel { ... }
@ViewModel
export class BarViewModel {
public foo1 = use(FooViewModel);
public foo2 = use(FooViewModel);
}
分页、筛选、弹窗这类通用逻辑,抽一次就能到处用,依赖关系明明白白。
组件生命周期:灵活且不设限
使用注解声明生命周期方法,不强制固定方法名。更爽的是,一个生命周期钩子可以绑定多个方法,进一步保证了类的纯净度和内聚性
ts
@ViewModel
export class FooViewModel {
@OnDidCreate
protected init() {
console.log("ViewModel created");
}
@OnDidMount
protected fetchData() {
console.log("拉数据");
}
@OnDidMount
protected initDict() {
console.log("初始化字典");
}
}
数据模型 + 元数据能力
前面说的 ViewModel 是管组件逻辑的,那纯数据呢?前端处理后端 JSON 时往往需要许多转化规则。于是我干脆把模型层也一并做进库里了,从接口数据到业务实例,一条龙用 Class + 装饰器搞定。
模型层声明
用 @Model 标记一个数据模型类,字段的映射规则、格式化、类型转换全用装饰器写在类上。后端返回的原始数据,丢进 reactive() 直接就是带类型、带业务方法的响应式实例,再也不用自己手写转换函数了。
ts
@Model
export class UserBo {
@JsonProperty("user_id") // 下划线字段自动映射
public id: string;
@JsonFormat("yyyy-MM-dd") // 日期自动格式化解析
public birthday: Date;
@Type(Dept) // 嵌套对象自动实例化
public dept: Dept;
}
// 后端原始数据直接转成响应式模型实例
const user = reactive(response.data, UserBo);
自定义注解,扩展性拉满
库内置了完整的元数据系统,你可以像 Java 那样自己写注解,给字段打标签,运行时直接读取配置。
做动态表单、配置化表格、低代码页面的时候特别好用 ------ 模型类写完,表单和表格直接照着元数据自动渲染,不用重复写配置。
ts
// 一行代码定义一个自定义注解
const Label = (text: string) => metadata('label', text);
const Required = metadata('required', true);
@Model
export class UserBo {
@Label("用户ID")
@Required
public id: string;
@Label("用户名")
public name: string;
}
// 运行时直接读取所有字段元数据
getMetadataValues(UserBo);
// { id: { label: "用户ID", required: true }, name: { label: "用户名" } }
编译方式随便选,我都兜底了
简单说,不管你项目是 TS 还是 Babel,是老版装饰器还是新提案,直接装上就能跑,不用为编译环境折腾配置。内部 6 套 vitest 配置覆盖了这些组合,不是写着玩的。
| 编译方式 | proposal | experimental | experimental + emitMetadata |
|---|---|---|---|
| typescript | ✅ 支持 | ✅ 支持 | ✅ 支持 |
| babel | ✅ 支持 | ✅ 支持 | ✅ 支持 |
常见问题
Q:内部用的是 setup 还是 Options API?
纯 setup 实现,所有测试都是在 __VUE_OPTIONS_API__: false 环境下跑的。ViewModel 不是 Vue 的子类,就是个被 setup () 消费的普通 class 实例。
Q:有 React 版吗?
有,但 React 版是我公司内部用的。这次开源的 Vue3 版本,是我吸取了之前 React 版的经验教训,完全重构的,设计上更成熟。
Q:你这刚发布的东西,能直接上生产吗?
不建议。
如果你认同我的设计思路,我的建议是:可以先点个关注、收个藏。等我把上层管理端和移动端的框架都完整重构到 Vue3 上的时候,才是我真正推荐大家上手用的时候。
这只是整个方案的底层基石,后面还有更上层的东西要放出来。
最后
项目刚发,还热乎着。
欢迎去仓库逛逛,有想法、有 bug 都可以提。更欢迎点个 Star 蹲后续,等上层框架出来了,直接上手成品体验会好得多。