前言
本文作为学习笔记,文中内容大多来自官方文档和一些资料,摘抄的部分会在文中标注出原文地址,可以直接参考原文。
上一篇学习了 TS 的基本数据类型、接口、函数和类等基本用法。接下来继续深入学习一些相对 JS 来说 TS 中新增的内容。
泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
下面通过一个例子来了解泛型,如何实现一个打印函数呢?
1 2 3 4
| function log (value: string): string { console.log(value) return value }
|
上面的这个 log
方法,只能接收和返回 string
类型数据。如何才能让该方法接收和返回 string[]
类型的数据呢?
第一种方法:函数重载,通过重载可以动态的匹配符合的类型。
1 2 3 4 5 6
| function log (value: string): string; function log (value: string[]): string[] function log (value: any): any { console.log(value) return value }
|
第二种方法:联合类型
1 2 3 4
| function log (value: string | string[]): string | string[] { console.log(value) return value }
|
联合类型会比函数重载会简洁一点。如果前面学的比较扎实,相信会想到 any 类型。
第三种方法:any 类型
1 2 3 4
| function log (value: any): any { console.log(value) return value }
|
这种方式比联合类型和函数重载都简洁,但是存在一个问题会丢失信息,即传入的类型和返回的类型应该相同。
如何优雅的解决这个问题呢?就是泛型,它可以不指定参数和返回值的类型,只有在真正使用的时候才去确定。
1 2 3 4 5 6
| function log<T> (value: T): T { console.log(value) return value } log<string>('a') log(['a', 'b'])
|
这种方式是不是更简洁,这只是基本用法。
TS 中的高级类型也广泛使用了泛型。如,泛型定义函数别名、泛型接口等。
1 2 3 4 5 6 7 8 9 10 11 12
| type Log = <T>(value: T) => T let myLog: Log = log
interface ILog<T = string> { (value: T): T; }
let myILog: ILog<number> = log
|
泛型类和泛型约束
泛型类和泛型接口的写法差不多,在类名后面加 <>
指定泛型类型。
1 2 3 4 5 6 7 8 9 10 11 12 13
| class CLog<T> { run (value: T) { console.log(value) return value } } let clog1 = new CLog<number>() clog1.run(10)
let clog2 = new CLog() clog2.run(['a', 'b'])
|
类可以支持多种类型,增强程序的扩展性。还可以通过泛型约束,灵活控制类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| interface Length { length: number }
function sLog<T extends Length> (value: T): T { console.log(value, value.length) return value }
sLog([1]) sLog('123') sLog({ length: 1 })
|
通过接口 Length
约束泛型。创建一个包含 .length
属性的接口,使用这个接口和 extends
关键字来实现约束。
小结
使用泛型有什么优点呢?
- 可以动态支持类型,增强程序的扩展性。
- 可以替代重载和联合类型声明,提高代码的简洁性。
- 泛型约束,灵活控制类型间的约束。
高级类型
交叉类型
所谓的交叉类型是将多个类型合并为一个类型。将现有的多个类型叠加在一起,它包含所需的所有类型的特性。使用 &
符合。
举个例子:
1 2 3 4 5 6 7 8 9 10 11
| interface DogInterface { run (): void } interface CatInterface { jump (): void }
let pet: DogInterface & CatInterface = { run () { }, jump () {}, }
|
PS: 交叉类型取的是所有类型的并集,而不是交集。
联合类型
前面也学到了联合类型,联合类型取得是两者中的一个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class ADDog implements DogInterface { run () { } eat () {} } class ADCat implements CatInterface { jump () { } eat () {} }
enum Master { Boy, Girl } function getPet (master: Master) { let pet = master === Master.Boy ? new ADDog() : new ADCat() pet.eat() return pet }
|
PS:联合类型取得所有类型的交集。这里取的 Dog | Cat
两者的交集。
可区分的联合类型
通过一个公共的字面量区分不同的类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| interface Square { kind: 'square', size: number }
interface Rectangle { kind: 'rectangle', width: number, height: number }
interface Circle { kind: 'circle', r: number }
type Shape = Square | Rectangle | Circle
function area (s: Shape) { switch (s.kind) { case 'square': return s.size * s.size case 'rectangle': return s.width * s.height case 'circle': return Math.PI * s.r ** 2 default: return ((e: never) => { throw new Error(e) })(s) } }
console.log(area({ kind: 'circle', r: 1 }))
|
索引类型
使用索引类型,编译器就能够检查使用了动态属性名的代码。
看一个 JS 例子。
1 2 3 4 5 6 7 8 9 10 11
| let obj = { a: 1, b: 2, c: 3 }
function getValues (obj: any, keys: string[]) { return keys.map(key => obj[key]) } console.log(getValues(obj, ['a', 'b'])) console.log(getValues(obj, ['e', 'f']))
|
使用索引类型可以添加类型约束改造,需要使用 索引类型查询 和 索引访问 操作符。
先看一下使用 TS 实现后结果。
1 2 3 4 5 6 7
|
function getValuesTS<T, K extends keyof T> (o: T, names: K[]): T[K][] { return names.map(key => o[key]) } console.log(getValuesTS(obj, ['a', 'b']))
|
编译器会检查数组中的元素是否是 obj
的一个属性。需要注意的地方,**keyof T
** 索引类型查询操作符。对任何类型的 T,keyof T
的结果为 T 上已知的公共属性名的联合。
1 2 3 4 5 6
| interface Obj { a: number, b: string } let key: keyof Obj
|
T[K]
索引访问操作符。作用就是 o[key]
具有的类型就是 obj['a']
,在普通上下文中使用 T[K]
就像使用索引类型查询一样。
映射类型
有时需要将已知类型的每个属性变成可选的或者只读的。TS 提供类从旧类型中创建新类型的一种方式:映射类型。
1 2 3 4 5 6 7 8 9 10 11 12
| interface Person { name: string; age: number; score: number }
type ReadonlyPerson = Readonly<Person>
type PartialPerson = Partial<Person>
type PickPerson = Pick<Person, 'name' | 'age'>
|
Readonly<T>
、Partial<T>
、Partial<T>
是 TS 库内置的映射类型。还有很多其他的映射类型,以上三种类型:属性列表中的 keyof T 且结果类型是 T[P] 的变体。这种转换称为同态,映射只作用于 T 的属性而没有其它的。
小结
本小结学习了 TS 中提供几种高级类型,先熟悉概念和用法后期慢慢深入。
参考