浅谈TS运行时类型检查

网站建设3年前发布
39 0 0

在编译阶段对变量类型进行静态检查,编译后的代码不保留任何类型标注信息,对实际代码运行没有影响,20230306151310123565292b9f91bf591585cecb0e66f5dde995661,在代码实际运行过程中对数据类型进行检查,一般会用在约束函数参数、返回值这类内外部之间传递数据,20230306151310e3b565830e069873d2d19250b5485f54e01086377,TypeScript 对于前端项目可维护性提升很大,也能帮我们保障内部编码时的类型安全,但在和外部进行数据传递时,仅仅有编译期类型检查还是免不了出一些问题,以我遇过的两次事故为例:,对内输入数据:线上接口返回的视频id字段类型由 string 变为 number 后前端获取后丢失精度,导致页面异常,向外输出数据:项目迭代需求时逻辑改动导致某个埋点字段丢失,过了很久要分析数据才发现,白白浪费了时间,如果我们在运行时做了相应的类型检查,发现异常上报监控,问题就能更早解决了,还有其他能想到的一些需要运行时类型检查的场景:,可以看出,在涉及IO数据场景时额外的运行时检查是有必要的,以使数据类型不符合预期时,我们能及时发现问题。,如上,我们可以手动编写一份运行时类型检查代码,但这样写起来效率低、维护性差,而且没有用上已有的TS类型,导致我们要同时维护两份类型,保证之间的同步。,下面向大家介绍业内几种类型检查方案,个人认为一个好方案至少要满足两点:,只需维护一份类型规则即可享有静态类型提示和运行时检查校验,在静态和运行时的类型检查能力等价(起码运行时不能比静态检查宽松,不然会出线上bug),编写运行时校验规则,并从中提取出静态类型,通过编写 JSON 来描述校验规则,典型的有 ajv、tv4,用法如下:,优点:,JTD 支持从已有的schema提取TS类型,避免维护两份类型定义,校验库都会提供一些常用的高级校验规则(如日期范围、邮箱格式等),JSON 格式易存储传输,甚至其他语言也能用,可以做到动态下发校验规则,缺点:,Schema 格式有额外学习成本,JSON 写起来太过冗长枯燥提示也不友好,实现原理:,类型检查:根据 schema 规则遍历比较数据字段,提取类型:结合 extends、infer、in keyof、递归等语法,20230306151142336eb9664f5210d97026711fae7400dc49a335232,通过调用API来描述组成校验规则,典型例子有 zod 、superstruct、io-ts,用法如下:,优点:,通过API组装类型的形式相比JSON更灵活和易编写,提供一些常用的高级校验规则(如日期范围、邮箱格式等),支持从已有的schema提取TS类型,避免维护两份类型定义,缺点:,有一些额外学习成本,不能直接运用我们已掌握的TS语法描述类型,实现原理:,和JSON形式类似,但实现更轻量(ajv有35k,zod只有10k),把静态类型和动态类型检查写在一起,主要是基于类属性装饰器来生成校验规则,典型例子有 class-validator、typeorm,用法如下:,优点:,强迫分原子类型,ORM风格,适合服务端场景使用,提供一些常用的高级校验规则(如日期范围、邮箱格式等),校验属性值的报错信息可以自定义,如@IsArray({message:'数组 不能为空'}),缺点:,运行时检查类型和TS类型都要写,但写在一块起码方便同步,校验规则需要声明class,特别是有嵌套对象时,写起来麻烦,只能检查类的实例,普通对象要配合 class-transformer 转换,实现原理:,装饰器+反射(通过装饰器给字段加入类型规则元数据,运行时再通过反射获取这些元数据做校验),通过处理 TS 类型,使之在运行时可用,典型例子有 typescript-json-schema,用法如下:,2023030615131514fbb7e58d987fb6ed13880eb2eec7985b1cf8623,20230306151315a228ebc37bdb7e710aa2205e25f3a9330b4980126,优点:,不需要手动维护两份类型定义,缺点:,本身不提供检查能力,需要配合额外校验库,部分TS类型语法不支持转换(如联合类型),有些规则需要另外学习它的注释语法,写起来也不方便,实现原理:,解析处理 TypeScript AST https://github.com/YousefED/typescript-json-schema/blob/master/typescript-json-schema.ts,在编译期将TS代码转成类型检查能力等价的JS代码,典型例子有 typescript-is、ts-auto-guard,用法如下:,配置 ts-loader 插件:,编译前源代码:,编译产物代码:,优点:,使用方便,无需维护两份类型和学习额外校验规则,只写TS代码就行,缺点:,每次会生成大片检查代码(即使类型存在复用),导致代码产物体积膨胀,校验能力完全依赖TS类型检查,不像校验库有一些高级规则(如日期范围、邮箱格式等),实现原理:,编写 TypeScript Transformer Plugin,运行机制类似 babel 插件(源码->解析语法树->修改语法树->转换),2023030615114444e9c7539c78381dbb132000706c67da3a57a8632,典型的方案有 DeepKit,基本上是把TS类型系统带到了JS运行时:,编译前源代码:,编译产物代码:,优点:,缺点:,实现原理:,在编译期将 TypeScript 类型信息转换成字节码(Bytecode),TS 类型信息都被完整保留到了运行时,之后在运行时用一个解释器计算出类型信息,我们在运行时也能使用它提供的丰富 API 反射类型信息,用在如生成 Mock 数据的场景。,没有十全十美的方案,综合来看当下使用如 zod 这类 API 形式的校验库会比较好,既成熟强大,也兼具灵活和易。,着眼未来 deepkit 似乎很有潜力,它其实是一整套 Web 开发方案,校验只是其中一部分,还有很多充分利用了运行时类型的功能特性。,那以后 TypeScript 会支持运行时类型检查吗?github 上也一直有人提相关的 issue,甚至有人专门建了一个请愿页面,但基本不太可能,因为 design goal 中已明确表示过不会增加任何运行时代码:,Add or rely on run-time type information in programs, or emit different code based on the results of the type system. Instead, encourage programming patterns that do not require run-time metadata.,

© 版权声明

相关文章