V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Hamm
V2EX  ›  Vue.js

Vue3+TypeScript 的另一种另类的打开方式

  •  
  •   Hamm · 2023-05-05 21:22:10 +08:00 · 2614 次点击
    这是一个创建于 572 天前的主题,其中的信息可能已经有所发展或是发生改变。

    ① 项目介绍

    项目的安装与初始化请先查看宿主项目

    https://gitee.com/hamm/AirPowerDemo

    为了满足前端开发标准化、工程化、系统化等等需求,我们设计并开发了一个开发组件库和常用类库和方法库的集合基础脚手架,其中包含了一些页面通用的布局、常用的弹窗和交互、提示信息以及网络请求,强类型面向对象的约束规范等,以满足日常开发的快捷、稳健、标准化等要求。

    ② 项目依赖

    AirPower 目前依赖 Vue + TypeScript + Element Plus + class-transformer 等组件,其中还包含了一些第三方模块的依赖,具体可以参考项目的 package.json

    ③ 模块说明

    AirPower 内置了以下一些模块:

    • component 组件

      包含了常用的 AInput AButton ATable AToolBar APage等常用的组件;

    • config 配置

      包含了 实体配置、字段在 Table Search Form 中的特性配置、Props传参配置、图像配置等常用的配置类、配置方法等;

    • decorator 装饰器:

      提供了表格配置、表单配置、类和属性自定义名称配置、搜索配置等装饰器;

    • dto 数据传输模型

      包含了一些分页、排序、常用类、抽象基类等标准的数据传输模型;

    • enum 枚举

      提供了目前已经达成标准和规范的一些枚举信息,如颜色、日期格式、反馈图标样式、搜索类型、HTTP 参数等;

    • feedback 反馈

      基于 Element Plus 提供的标准反馈组件,定义了规范的一些弹窗、确认框、通知信息等反馈组件;

    • helper 助手类

      提供了时间日期、文件、数据转换、弹窗、随机与加解密、路由等快捷操作的助手类和方法;

    • interface 接口

      提供了一些常用数据结构的标准化配置以及需要被实现的标准接口;

    • model 数据模型

      提供一些基础的数据模型与操作方法;

    • service 服务

      提供了一些基础的 CRUD 方法的基础网络请求服务和一些常用的 API

    • view 页面

      提供了错误页等常用的内置页面;

    ④ 开发理念

    • CaaS 配置即服务

      为了避免大量重复代码的出现,我们尽可能通过 TypeScript 的装饰器将一些类或属性的配置写入到原型链中,在需要使用这些配置的地方通过反射将保存的参数配置取出使用即可。

      其中,如果涉及一些默认值,将默认值通过 AirConfig 作为基础配置进行初始化到系统中,通过配置项的使用范围和配置成本进行调整后,使用方可以最小配置的方式进行系统业务的接入和开发。

    • 强面向对象的支持

      在相同或相似业务中,我们使用标准的强面向对象进行实现,基于继承、封装等特性进行代码的复用,通过泛型、接口等方式将相似的业务逻辑进行标准化约束,实现相似但又不完全相同的一些灵活业务的快速开发。

    • 数据和类的转换器

      为了在后端因业务要求进行属性调整后前端不需要做大量的查找替换进行修复,我们引入了 class-transformer 这个第三方库进行数据转换,通过自由选择 Expose 字段和配置转换规则来实现仅在配置层即可解决字段属性变更的问题。详细请阅读关于数据转换的部分。

    ⑤ 设计要点

    • 1️⃣ TypeScript 装饰器篇

      装饰器是 TypeScript 引入类似 Java 注解 的一个编程方式,依赖装饰器,可无侵入式的在业务前后运行其他的业务代码而无需改动原有的业务代码。

      装饰器的本质就是一个 Function 类、方法、属性、参数等在标记了装饰器后,会执行装饰器的相关方法,然后通过原型链或者反射的方式,对操作的对象进行一些数据的处理或逻辑的限制。

    • AirPower 使用的普通装饰器

      @ClassName('用户') //为类标记一个可读的名称
      @FieldName('真实姓名') //为属性标记一个可读的名称
      @Description('这是用户的名称') //为属性标记一个可读的描述
      

      标记了 ClassName FieldName Description 装饰器的类或属性,可使用类的原型或者类的对象进行获取

      使用场景介绍

      当我们需要频繁的修改某些实体的名字,如 AppEntity 从 应用 改为 应用程序,姓名改为真实姓名,我们往往需要去查找替换,所以我们直接标记到对应的 AppEntity 类上,其他地方统一从实体中直接获取。

    • AirPower 使用的表格配置装饰器

      @TableField() //为实体类的属性标记 是否为表格使用的字段
      

      TableField 注解支持传入一个 ITableFieldConfig 接口约束下的 JSON 配置项,可对表格的列名称、宽度、是否自定义列、是否枚举列等进行一系列的配置。

      使用时,可通过类的原型或类的对象进行读取并传入给 ATable 组件。获取方法如下:

      UserEntity.prototype.getTableFieldConfigList()
      //或者
      const user = new UserEntity()
      user.getTableFieldConfigList() //用户列表的表格显示字段配置信息数组
      
    • AirPower 使用的搜索配置装饰器

      @SearchField() //为实体类的属性标记 是否为搜索使用的字段
      

      SearchField 注解支持传入一个 ISearchFieldConfig 接口约束下的 JSON 配置项,可对搜索的输出 key 、是否枚举列等进行一系列的配置。

      使用时,可通过类的原型或类的对象进行读取并传入给 AToolBar 组件。获取方法如下:

      UserEntity.prototype.getSearchFieldConfigList()
      //或者
      const user = new UserEntity()
      user.getSearchFieldConfigList() //用户搜索字段配置信息数组
      
    • AirPower 使用的表单配置装饰器

      @FormField() //为实体类的属性标记 是否为表单使用的字段
      

      FormField 注解支持传入一个 IFormFieldConfig 接口约束下的 JSON 配置项,可对搜索的输出 key 、是否枚举列等进行一系列的配置。

      使用时,可通过类的原型或类的对象进行读取并传入给 AInput 组件。获取方法如下:

      <AInput v-model.name="user.name" :entity="UserEntity"/>
      
    • 2️⃣ 数据转换篇

    • class-transformer 简介

      在日常的 API 接口对接过程中,接口中难免产生很多无用的属性,亦或者某些属性需要前端进行转换后才能使用,AirPower 采用了 类 + class-transformer 库来完成前端和后端的接口数据处理。

      class-transformerTypeScript 下一个基于 装饰器 + Reflect-metadata 实现的一个数据转换库,能根据实体属性上标记的相关装饰器规则进行 类到 JSONJSON 到类 的转换,具体的相关操作文档可以查看 npm-class-transformer

    • class-transformer 的装饰器

      //将 userName 属性暴露为 username 来处理后端和前端字段不统一的问题
      @Expose({name:"username"}) userName!: string;
      //标记属性不被来去转换 AirModel 中配置必须 Expose 所以这个一般很少使用
      @Exclude() password
      //标记属性必须被转为什么类型 支持简单类型 复杂类型
      @Type(()=>String) password
      //自定义转换规则 可在指定的转换方向上进行数据的转换 
      //如时间戳转可视化时间等操作
      @Transform(({value}) => {
             // custom transform code here
             return something;
      },{ toClassOnly | toPlainOnly : boolean })
      

      当然, 我们将 class-transformerReflect-metadata 进行了一系列的封装, 请看下面的内容.

    • AirClassTransformerHelper 转换助手类

      我们将上面的两个库进行了更适合我们的业务封装, 实现了 AirClassTransformerHelper 一系列的装饰器和反射高级编程的功能.

      其中提供了两个静态方法:

      // 将普通的 JSON 对象转换到声明的实体类中, 方便字段的调用和数据转换
      AirClassTransformerHelper.parse()
      // 将一个对象拷贝到一个新的对象上, 避免深浅拷贝的尴尬
      AirClassTransformerHelper.copy()
      
    • 3️⃣ 面向对象篇

    • AirModel 基础模型类

      AirModelAirPower 中通过 class-transformer 库 实现了 json 到类 互相转换的一系列方法的一个数据模型基类。 我们建议,所有与后端对接的数据类都必须根继承至 AirModel, 比如 数据实体 分页对象 搜索对象 等等。然后你就可以通过 class-transformer 提供的相关装饰器对指定数据类进行字段的暴露、转换等操作。

    • AirEntity 实体基类

      AirEntity 约束子类必须包含 ID 字段,请自行实现一个继承 AirEntity 实体基类的 BaseEntity 来进行比选字段的暴露和配置,也可以在 BaseEntity 中添加子系统业务相关的其他必须的业务字段。

    • 使用面向对象继承减少重复接口的声明

      我们在 AirPower 里实现了一个 AirAbstractService 服务基类,实现了单表 增加删除修改查询分页查询数组 等相关基础方法,业务中如无特殊业务需求,无需再次实现单表业务中这些重复的方法,但我们建议:宿主初始化一个继承了AirAbstrtactService 的中间基类 BaseService 以应对不符合 AirPower 规范的场景,你可以在 BaseService 中间接口类中重写相关的接口。

    • 在合理的场景下使用 interface 作为数据结构

      interface 在面向对象中可作为约束来对业务进行规范,但 TypeScript 很多使用者都习惯使用 interface 来作为限制对象属性的工具,我们建议:

    只在装饰器参数、与第三方库的数据转换场景下使用 interface 作为数据格式的直接限制使用,其他任何场景不使用 interface 作为直接限制。

    通过 Demo 宿主项目来体验一把

    使用说明

    我们使用的是 Vite 构建, 包管理使用的是 yarn, 请先安装前叙的相关工具后继续接下来的操作 :)

    核心包仓库: https://gitee.com/hamm/airpower

    一、初始化仓库(推荐 ssh)

    建议 windows 开发者使用 git bash 不要使用 windows 自带的拉垮的 cmd powershell 等。否则接下来的脚本可能出现问题,你只能通过自己手动去操作。

    任选一个方式的脚本一键初始化项目

    • ssh 方式
    git clone [email protected]:hamm/AirPowerDemo.git &&
    cd AirPowerDemo/src && 
    git clone [email protected]:hamm/airpower.git && cd ../ &&
    yarn && cp .env.dev .env && yarn s
    
    • https 方式
    git clone https://gitee.com/hamm/AirPowerDemo.git &&
    cd AirPowerDemo/src && 
    git clone https://gitee.com/hamm/airpower.git && cd ../ &&
    yarn && cp .env.dev .env && yarn s
    

    二、修改环境变量

    按需修改配置

    VITE_APP_NAME = "开发环境"
    VITE_APP_API_URL = "/api/"
    VITE_APP_STATIC_URL = "/static/"
    

    三、启动和打包

    启动项目前,我们建议你关闭 visual studio codeVetur 插件,避免 vue2vue3 产生冲突。

    # 启动项目
    yarn s              #缩写指令
    
    # 打包项目
    yarn dev            #开发环境 使用.env.dev
    yarn test           #测试环境 使用.env.test
    yarn production     #生产环境 使用.env.production
    

    四、其他命令

    #使用标准 commit 模板
    yarn c   
    
    #更新项目和 AirPower
    yarn u   
    
    #查看 Git 格式化日志
    yarn l           
    

    五、推荐的 VSCODE 插件扩展

    • TypeScript Vue Plugin (Volar)
    • Vue Language Features (Volar)
    • ESLint
    • SCSS Formatter

    如碰到其他兼容问题,建议在工作区禁用以上四个插件之外的其他插件,特别是Vetur

    And now, enjoy your coding!

    8 条回复    2023-06-14 11:41:48 +08:00
    Aloento
        1
    Aloento  
       2023-05-05 21:23:14 +08:00
    嗯,脚手架呢
    Hamm
        2
    Hamm  
    OP
       2023-05-05 21:24:54 +08:00
    @Aloento 第一步有 demo 仓库哦
    stkstkss
        3
    stkstkss  
       2023-05-05 22:31:17 +08:00 via iPhone
    拉到最后需要 3 个秒
    lanten
        4
    lanten  
       2023-05-06 09:23:14 +08:00
    电磁滚轮只要 0.5 个秒
    sjhhjx0122
        5
    sjhhjx0122  
       2023-05-06 17:06:15 +08:00
    用 vim 到底只需要一瞬间
    agileago
        6
    agileago  
       2023-05-06 17:17:43 +08:00
    大兄弟,搞得太复杂,前端就不要用继承了,你想用面向对象的话可以试试我的 vue3-oop https://github.com/agileago/vue3-oop

    服务+依赖注入 搞定所有业务逻辑
    Hamm
        7
    Hamm  
    OP
       2023-05-06 20:48:46 +08:00
    @agileago 看了下,觉得更复杂了。

    没有继承的面向对象没有了灵魂。
    yetrun
        8
    yetrun  
       2023-06-14 11:41:48 +08:00
    https://gitee.com/hamm/AirPowerDemo

    打不开,Why Why Why ?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3152 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 13:42 · PVG 21:42 · LAX 05:42 · JFK 08:42
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.