这篇博客主要探讨了如何测试复杂的 TypeScript 类型是否按预期工作,需要在类型级别使用断言和其他工具。
- 断言在类型级别:编写更复杂的类型如同在不同级别编程,在程序级别使用 JavaScript,在类型级别使用 TypeScript。在程序级别可使用
assert.deepEqual()
测试代码,在类型级别则需要类似的断言,如type Pair<T> = [T, T]; type Result = Pair<'abc'>; type _ = Assert<Equal<Result, ['abc', 'abc']>>;
,这里的Assert
和Equal
来自asserttt
包。 如何检查两个类型是否相等:
- 朴素解决方案:两个类型
X
和Y
相等当且仅当X
扩展Y
且Y
扩展X
,通过type SimpleEqual1<X, Y> = X extends Y? (Y extends X? true : false) : false;
实现,但对于联合类型会出现问题。 - 禁用分布:为使
SimpleEqual
按预期工作,需关闭分布,将extends
两边变为单元素元组,如type SimpleEqual2<X, Y> = [X] extends [Y]? ([Y] extends [X]? true : false) : false;
,可正确处理联合类型,但无法区分any
。 - 严格比较
any
:通过一个技巧type StrictEqual<X, Y> = (<T>() => T extends X? 1 : 2) extends (<T>() => T extends Y? 1 : 2)? true : false;
来区分any
和其他类型。
- 朴素解决方案:两个类型
- 如何断言某事物必须为
true
:在程序/JavaScript 级别可通过抛出异常进行断言,在 TypeScript 中无法在编译时失败,但可通过type AssertType2<_B extends true> = void;
这种 workaround 来实现,不过有其局限性,如type AssertEqual<X, Y> = true extends Equal<X, Y>? void : Fail;
只能通过Fail
实现某些功能。还可通过Not
类型来断言一个类型不等于另一个类型,Not
是一个谓词类型,asserttt
还定义了其他谓词类型。 - 断言错误:在 JavaScript 级别可使用
assert.throws()
,在类型级别可使用@ts-expect-error
,默认它只检查错误是否存在,要检查具体错误可使用ts-expect-error
包。 - 断言值的类型:测试类型时可通过
typeof
运算符或assertType()
函数来检查值的类型,assertType()
函数可直接接受程序级别的值进行静态检查,但只能检查可赋值性,不能检查类型相等性,而Equal
可用于检查值的类型是否不具有比给定类型更具体的类型。 - 在普通代码中使用类型级断言的用例:在普通(非测试)代码中,类型级断言也很有用,如对于有类型
Person
和包含其键的数组personKeys
,可使用类型级断言type _1 = Assert<Equal<keyof Person, (typeof personKeys)[number]>>;
来让编译器在personKeys
出错时发出警告。 - 运行类型级测试:运行 TypeScript 编写的普通测试需运行编译后的 JavaScript,若测试包含类型级断言,还需额外进行类型检查,可先运行 JavaScript 再用
tsc
进行类型检查,或使用tsx
等工具在运行代码前进行类型检查。 - 进一步阅读:2ality 博客文章“Testing static types in TypeScript”提供了各种类型测试方法的概述。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。