ts
接口
对象接口:约束对象字段
interface FullName{ firstName:string; secondName?:string; // 加上问号,表示可选参数 }
注意一个叫额外属性的检查的东西:
function printName(name: FullName) { console.log(`${name.firstName} --- ${name.secondName}`); }
像下面这样调用函数传参会报错,说多了一个team。
printName({ firstName:'duncan', secondName:'tim', team:'spurs' }) // error
但是如果将这个对象赋值给一个变量,再用这个变量作为参数传递就能跳过这种检查
var someone = { firstName: "duncan", secondName: "tim", team: "spurs" }; printName(someone); // ok
也可以用as来跳过检查
printName({ firstName:'duncan', secondName:'tim', team:'spurs' } as FullName)
函数接口:约束函数参数和返回值
interface encrypt { (key:string, value:string):string } let en:encrypt = function (k:string, v:string):string{ return k+v }
类接口:约束类属性和方法
interface User1 { name: string; // 必须要有name属性,且为string类型 sayHello(someword: string): void; // 必须要有sayHello方法 } class RegularUser implements User1 { name: string; constructor(name: string) { this.name = name; } sayHello(str: string) { console.log(`${this.name} says: ${str}`); } }
接口拓展
interface PlayerUser extends User1 { team: string; play(): void; }
泛型
泛型函数
定义:
function identity<T>(arg: T): T { return arg; }
跟普通的函数定义多了个<T>,可以看作是:之后(形参,函数体)都需要用T,所以得先声明T。
使用:
let output = identity<string>("myString");
调用的时候,传入特定类型string来锁定T类型。
泛型类型
那么泛型函数的类型也就多一个<T>
let myIdentity: <T>(arg: T) => T = identity;
或者这样写:
let myIdentity: {<T>(arg: T): T} = identity;
个人看法,这种写法有点像上面的函数接口写法。果然马上下面就开始讲泛型接口了。
泛型接口
interface GenericIdentityFn { <T>(arg: T): T; } function identity<T>(arg: T): T { return arg; } let myIdentity: GenericIdentityFn = identity;
泛型接口还有一种写法,把T当作接口的参数,这样整个接口内都能使用到T了,使用的时候传入具体类型:
interface GenericIdentityFn<T> { (arg: T): T; } let myIdentity: GenericIdentityFn<number> = identity;
泛型类
和泛型接口类似,定义的时候后面多一个<T>
class GenericNumber<T> { zeroValue: T; add: (x: T, y: T) => T; } let myGenericNumber = new GenericNumber<number>();
泛型约束
因为泛型类型可以是任意类型,因此里面有哪些字段完全不确定,而当你确定T里面一定需要哪些字段的时候就需要对泛型类型进行约束了。如下例,谁也不能保证T里有length字段,因此报错。
function loggingIdentity<T>(arg: T): T { console.log(arg.length); // Error: T doesn't have .length return arg; }
定义一个接口来约束T,这样就保证了T中一定会有什么。
interface Lengthwise { length: number; } function loggingIdentity<T extends Lengthwise>(arg: T): T { console.log(arg.length); // Now we know it has a .length property, so no more error return arg; }
类型兼容性
比较两个函数
let x = (a: number) => 0; let y = (b: number, s: string) => 0; y = x; // OK x = y; // Error
这样理解,y = x的时候,最终调用签名是(b: number, s: string) => 0;
,也就是说调用的时候得传入两个参数,但是实际用到的只有第一个参数而已,忽略传入的参数当然是被允许的。
同理,x = y的时候,x的签名只能传入一个参数,但是赋值的y类型函数却需要两个参数,这当然是不行的。
因此,如果y的签名写成(b: number, ?s: string) => 0;
就不会报错。
枚举
枚举类型与数字互相兼容,但是枚举类型之间不兼容,虽然枚举类型实际上的值都是数字。
高级类型
交叉类型 Intersection Types
a & b 表示组成一种新的类型,相当于并操作,新类型包含a,b中的字段。
联合类型 Union Types
a | b 表示该类型是a 或者b。
interface Bird { fly: () => void; layEggs: () => void; } interface Fish { swim: () => void; layEggs: () => void; } function getSmallPet(): Fish | Bird { class f implements Fish { swim() {} layEggs() {} } return new f(); }
被这样定义的变量,只能使用a和b共有的字段,比如下面这样不行,因为只能使用共有的字段layEggs。
let pet = getSmallPet(); if (pet.swim) { // error pet.swim(); } else if (pet.fly) { // error pet.fly(); }
那么要如何确定到底是那种类型呢?类型断言
if ((<Fish>pet).swim) { (<Fish>pet).swim(); } else { (<Bird>pet).fly(); }
如果嫌这种方法麻烦可以直接用typeof或者instanceof来判断
用户自定义的类型保护
上面这种断言如果到处要用的话就得重复写,造成大量重复代码。可以定义成函数,反复调用。这个叫用户自定义的类型保护。里面有个 parameterName is Type
这种形式的语法, parameterName
必须是来自于当前函数签名里的一个参数名。
function isFish(pet: Fish | Bird): pet is Fish { return (<Fish>pet).swim !== undefined; } if (isFish(pet)) { pet.swim() } else { pet.fly() }
TypeScript不仅知道在 if分支里 pet是 Fish类型; 它还清楚在 else分支里,一定 不是 Fish类型,一定是 Bird类型。这一点也可以在代码提示上看出来。
null
function fun(arg: string | null): number { return arg.length // error }
arg可能为null,那么可以用===来做判断,也可以用短路方式来避免错误,还可以加叹号!来去除null。
function fun(arg: string | null): number { return arg!.length // ok }
映射类型
这玩意有点意思,映射通常来说操作的都是数据,但是这里操作的是类型,将一个类型映射(转换)成一种新的类型。既然是操作类型,那当然就要用泛型,因为需要传入的参数是类型。
下面的代码将一个类型的所有属性都设置成只读,映射出一种新类型。
interface Per { name: string; age: number } type ReadonlyType<T> = { readonly [P in keyof T]: T[P] } let rp: ReadonlyType<Per> = { name: 'kobe', age: 99 } rp.name = 'uuu' // error
主要是熟悉[P in keyof T]: T[P]
这种写法,遍历类型中所有的key,然后索引得到对应key的值(也就是string,number这些的),批量操作。并且,P in keyof T
取到的属性是包含属性修饰符的,即如果Per中name是被readonly或者?修饰的,那么,keyof取到的属性也同样带这些修饰符。
还可以这样用
type Keys = 'option1' | 'option2'; type Flags = { [K in Keys]: boolean }; let ffs: Flags = { option1: false, option2: true }
因此ts提供了一些工具,像Partial,Readonly之类的封装了这些操作。
infer
type FunWithMappedArgt<P extends { [key: string]: any }> = (args: P) => any; type DestructuredArguments<F extends FunWithMappedArgt<any>> = F extends FunWithMappedArgt<infer R> ? R : never;
FunWithMappedArgt类型为一个通用的函数类型,DestructuredArguments就接受一个通用类型的函数,然后返回该函数的参数类型。
infer实际上是一个声明关键字,我们可以用它来声明一个变量, 只能在extends
条件语句中使用infer
关键字