也许跟大家不太一样,我是这么用 TypeScript 来写前端的 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Hamm
V2EX    前端开发

也许跟大家不太一样,我是这么用 TypeScript 来写前端的

  •  
  •   Hamm 2023-08-14 11:55:16 +08:00 2501 次点击
    这是一个创建于 790 天前的主题,其中的信息可能已经有所发展或是发生改变。

    一、当前一些写前端的骚操作

    先罗列一下见到过的一些写法吧:)

    1. interface(或 Type)一把梭

    掘金上很多文章,一提到 TypeScript,那不得先用 interface 或者 type 来声明个数据结构吗?像这样:

    type User = { nickname: string avatar?: string age: number } interface User { nickname: string avatar?: string age: number } 

    然后其他方法限制下入参类型,搞定,我掌握了 TypeScript 了,工资不得给我涨 3000 ???

    这里说明一下, 我司 不允许 直接使用 interface type 来定义非装饰器参数和配置性参数之外其他 任何数据类型

    2. 类型体操整花活

    要么把属性整成只读了,要么猪狗类型联合了,要么猪尾巴搞丢了,要么牛真的会吹牛逼了。

    类型体操确实玩出了很多花活。 昨天说过了:TypeScript 最好玩的就是类型体操, 也恰好是最不应该出现的东西

    3. hook 的无限神话

    不知道什么时候开始,hook 越来越流行。 听说不会写 hook 的前端程序员,已经算不上高阶程序员了, 不 use 点啥都展示不出牛逼的水平。

    4. axios 拦截大法好

    随便搜索一下 axios 的文章, 没有 拦截器 这个关键词的文章都算不上 axios 的高端用法了。

    二、 我们一些不太一样的前端骚操作

    昨天的文章有提到一些关于在前端使用 装饰器 来实现一些基于配置的需求实现, 今天其实想重点聊一聊如何在前端优雅的面向对象。

    写过 JavaSpringBootJPA 等代码的后端程序员应该非常熟悉的一些概念:

    • 抽象: 万物都可抽象成相关的类和对象
    • 面向对象: 继承、封装、多态等特性的面向对象设计思维
    • 切面: 没有什么是切一刀解决不了的,如果一刀不行, 那就多来几刀。
    • 注解: 没有什么常量是不能使用注解来配置的, 也没有什么注解是切面想切还能躲得掉的
    • 反射: 没有什么是暴力拿取会失败的, 即使失败也没有异常是丢不出来的
    • 实体: 没有什么是不能抽象到实体上的, 万物皆唯一。
    • 很多: 还有很多,以上描述比较主观和随意。

    于是我们开始把后端思维往前端来一个个的转移:)

    1. 抽象和面向对象

    与后端的交互数据对象、 请求的 API 接口都给抽象到具体的类上去,于是有了:

    • Service API 请求类
    abstract class AbstractService{ // 实现一个抽象属性 让子类们实现 abstract baseUrl!: string // 再实现一些通用的 如增删改查之类的网络请求 // save() // getDetail() // deleteById() // select() // page() // disabled() // ...... } 
    • Entity 数据实体基类
    abstract class AbstractBaseEntity<S extends AbstractService> { abstract service!: AbstractService // 任何数据都是唯一的 ID id!: number // 再来实现一些数据实体的更新和删除方法 save(){ await service.save(this.toJson()) Notify.success("新增成功") } delete(){ service.deleteById(this.id) Notify.success("删除成功") } async validate(scene: EntityScene):Promise<void>{ return new Promise((resolve,reject)=>{ // 多场景的话 可以 Switch if(...){ Notify.error("XXX 校验失败") reject(); } resove(); }) } // ...... } 
    • 子类的实现:)
    class UserEntity extends AbstractUserEntity<UserService>{ service = new UserService() nickname!: string age!: number avatar?: string // 用户是否成年人 isAdult(): boolean{ return this.age >= 18 } async validate(scene: EntityScene): Promise<void> { return new Promise((resove,reject)=>{ if(!this.isAdult()){ Notify.error("用户未成年, 请确认年龄") reject(); } await super.validate(scene) }) } } 
    • View 视图调用
    <template> <el-input v-model="user.nickname"/> <el-button @click="onUserSave()">创建用户</el-button> </template> <script setup lang="ts"> const user = ref(new UserEntity()) async function onUserSave(){ await user.validate(EntityScene.SAVE); await user.save() } </script> 

    2. 装饰器/切面/反射

    装饰器部分的话,昨天的文章有提到一些了,今天主要所说反射和切面部分。

    TypeScript 中, 其实装饰器本身就可以理解为一个切面了, 这里与 Java 中还是有很多不同的, 但概念和思维上是基本一致的。

    反射 ReflectTypeScript 中比较坑的一个存在, 目前主要是依赖 reflect-metadata 这个第三方库来实现, 将一些元数据存储到 metadata 中, 在需要使用的时候通过反射的方式来获取。 可以参考这篇文章:TypeScript 中的元数据以及 reflect-metadata 实现原理分析

    在实际使用中, 我们早前用的是 class-transformer 这个库, 之前我对这个库的评价应该是非常高的: “如果没有 class-transformer 这个库,TypeScript 狗都不写。”

    确实很棒的一个库,但是在后来,我们写了个通用的内部框架, 为了适配 微信小程序端 以及 uniapp 端, 再加上有一些特殊的业务功能以及 class-transfromer 的写法和命名方式我个人不太喜欢的种种原因, 我们放弃了这个库, 但我们仿照了它的思想重新实现了一个内部使用的库,做了一些功能的阉割和新特性的添加。

    核心功能的一些说明

    • 通过反射进行数据转换

      如将后端 API 返回的数据按照前端的数据结构强制进行转换, 当后端数据返回乱七八糟的时候,保证前端数据在使用中不会出现任何问题, 如下 demo

      class UserEntity { @Type(String) phone!: string; @Type(RoleEntity) roleInfo!: RoleEntity: @Type(DeptEntity) @List @Default([]) deptInfoList!: DeptEntity[] @Type(Boolean) @Default(false) isDisabled!: boolean } 
    • 通过反射进行配置的存储和读取

      这个在昨天的文章中有讲到一部分, 比如配置表单、表格、搜索框、权限 等

    3. 再次强调面向对象

    为了整个前端项目的工程化、结构化、高度抽象化,这里不得不再次强调面向对象的设计:)

    • 这是个拼爹的社会

      一些通用的功能,一旦有复用的可能, 都可以考虑和尝试让其父类进行实现, 如需要子类传入一些特性参数时, 可以使用抽象方法或抽象属性(这可是 Java 中没有的)来传入父类实现过程中需要的特性参数。

    • 合理的抽象分层

      将一些特性按照不同的抽象概念进行组合与抽离,实现每个类的功能都是尽可能不耦合,实现类的单一职责。如存在多继承, 在考虑实现类的实现成本前提下,可考虑抽象到接口 interface 中。

    • 还有很多,有空再一一列举

    4. 严格但又有趣的 tsdoc

    我们先来看一些注释的截图吧:)

    image.png

    image.png

    image.png

    一些详细的注释、弃用的方法、选填的参数、传入参数后可能影响或依赖的其他参数,在注释里写好玩的 emoji或者图片,甚至是 直接在注释里写调用 demo, 让调用方可以很轻松愉快的对接调用, 玩归玩, 确实对整体项目的质量有很大的帮助。

    三、 写在最后

    中午跟同事吃饭聊了聊现在国内大前端的一个状态, 当时聊到一个关键词 舒适区, 还有前端整个技术栈过于灵活的一些优缺点, 几个大老爷们都发出了一些感慨, 如果前端能够更标准化一些, 像 Java 一样, 说不定前端还能上升几个高度。

    That's all, 今天水的文章到此结束。

    代码已经在 Github 开源: https://github.com/HammCn/AirPower4T

    7 条回复    2023-08-14 15:32:29 +08:00
    manasheep
        1
    manasheep  
       2023-08-14 11:59:18 +08:00
    直接用 Blazor ,C#写前端
    walpurgis
        2
    walpurgis  
       2023-08-14 12:18:16 +08:00
    angular 欢迎你
    这些操作在 OOP 代码中很常见,不算骚,没有流行开的原因只是因为 React/Vue 都搞 hooks ,推荐代码风格都往 FP 走了,这套东西没法用
    Hamm
        3
    Hamm  
    OP
       2023-08-14 12:37:14 +08:00
    @walpurgis 就是从 ag 中抄过来的,公司现在用 Vue ,抄过来塞进去了。
    Pencillll
        4
    Pencillll  
       2023-08-14 13:09:29 +08:00
    写文章时至少放一些有效的代码吧……比如这里 `abstract baseUrl!: string` 会报错,还有在非 async 函数里用 await 这种错误
    实体类和注解这个确实很像后端了,前端没有 orm 的加持要搞这种感觉会带来不少麻烦(序列化、组合之类),而且 前端 oop 也有些坑,比如父类是个同步方法,子类想要改成异步的就头疼了

    > 我司 不允许 直接使用 interface type 来定义非装饰器参数和配置性参数之外其他 任何数据类型。
    这个我比较感兴趣,能展开讲讲吗,为啥不允许用 interface type
    Hamm
        5
    Hamm  
    OP
       2023-08-14 14:43:36 +08:00
    @Pencillll 你说得对,文中的代码都是在文章编辑器中撸出来的,没有在代码编辑器中验证。
    目前生产上的代码和开发中的代码,写着目前还没有很多头疼的事情发生。

    不允许的原因,可能是根本的原因就是下面这张图
    https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5e9097f4a344408d8b3fa0191f64e121~tplv-k3u1fbpfcp-watermark.image

    前端直接使用 interface 和 type 声明,等于把一些瞎搞的属性名称硬编码了,无法使用装饰器这些方案来方便的转换。
    之前就写了很多转换方法,但已经为时已晚,得自己去各种出口入口的地方加上转换方法来应对后端的瞎搞。
    Hamm
        6
    Hamm  
    OP
       2023-08-14 14:45:19 +08:00
    @Pencillll 如果对接的 api 接口改动不频繁,或者是实现很标准,我觉得应该不至于现在这个鬼样子。
    相反,因为后端的不规范,现在才出了这套方案。
    当然,比起 orm 来说,差得东西还很多,但对于这种后端瞎搞的场景,已经基本支持了。
    Pencillll
        7
    Pencillll  
       2023-08-14 15:32:29 +08:00
    @Hamm 理解,我有时候也会遇到这种困境,用注解进行转换看起来挺不错的,以后尝试一下
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     862 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 19:33 PVG 03:33 LAX 12:33 JFK 15:33
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86