TypeScript 类型编程
本文最后更新于:2020年12月29日 凌晨
前言
TypeScript 改变了吾辈对于类型系统的认知,它强大的类型系统使得类型本身也是可编程的。
最近 TypeScript 更新了一个大版本 v4,新增了一些非常强大的特性,让之前难以做到的事情也能够实现了。
- 模板字符串类型
- 递归类型
简介
模板字符串类型
尤其适合在 CSS Properties 相关的类型定义中使用,例如 css 的 width
,就可以使用模板字符串进行检查避免低级错误。
之前
1 |
|
现在
1 |
|
该特性在 Grid 组件进行了实用,能够避免一些低级错误。
递归类型
事实上,递归类型的需求由来已久。例如典型的 Array.prototype.flat
函数的类型定义,或是函数式中部分应用函数的类型定义。
下面是一个将嵌套数组亚平的函数的类型定义示例(来自 TypeScript 官网)
1 |
|
实际的使用场景
在 i18next 中根据 key 获取翻译字符串
最近遇到了通过 i18n 框架获取翻译文本的需求,其中翻译文本通过一个对象的形式定义,所以吾辈就需要一种能够根据 key 获取到类型的方法。
需要支持以下情况
- 根据 key 获取对应的文本
- 根据 key 深层获取文本
- 根据 key 获取文本并进行参数注入
下面是吾辈对 i18next 的封装
1 |
|
使用起来很简单
1 |
|
接下来,我们分析以下 CheckDictString
1 |
|
- 传入泛型参数
T
和O
,T
必须继承自string
- 判断
T
是否继承自${infer A}.${infer B}
,即判断T
是否包含.
,并解构得到A
(第一个.
之前),B
(第一个.
之后,可能还包含.
)- 如果是,则继续判断
A
是否为传入对象的字段- 如果是,则继续递归检查
B
是否为O[A]
的一个字段 - 否则,返回
never
- 如果是,则继续递归检查
- 否则,则判断
T
是否是O
的字段- 如果是,则返回
T
- 否则,返回
never
- 如果是,则返回
- 如果是,则继续判断
可以看到,如果检查出现错误,则返回 never
,但我们传入的 string
是不能合并为 never
的,这将会导致 ts 类型检查出错(其它的类型基本上也是一样的推导方式)。
附录
虽然看起来不错,那么这个类型是否满足我们简化 i18next 使用的需求呢?
实际上没有。即便有如此强大的类型系统,但它仍然不足以满足特别灵活的需求。实际使用时仍发现以下问题:
- 嵌套对象的参数注入没有进行检查
- 参数注入的翻译文本没有提示注入参数
解决方案有两个方向
- linter rule
- code generate
下面是一个简单的对比
分类 | typescript | linter rule | code generate |
---|---|---|---|
使用 | 直接使用 | 通过 eslint 插件 | 通过 cli 命令行 |
复杂度 | 一般(不用了解 ast ) |
高 | 较高 |
适用场景 | 绝大多数场景 | 容易编写的少量代码的检查 | 大量重复可自动化生成的代码 |
使用类型系统解析 json 字符串(好玩性质)
twitter 上 甚至有人使用 TypeScript 的模板字符串和递归类型解析了 json 字符串。
1 |
|
我想后端语言(Java/GoLang)至今也没有出现如此复杂的类型定义。
总结
虽然 TypeScript 的类型系统已经如此强大,但它并非没有局限性,像在上面的 在 i18next 中根据 key 获取翻译字符串 便是一例。