typescript 类型系统 TypeScript 中,每个类型就是一个集合
基本类型 字面量类型/单元类型 typescript
允许模拟定义具体值类型
1 2 3 4 5 let str : "wdnmd" = "wdnmd" ;let str1 : "red" | "blue" | "green" = "red" ;let str2 : "color" | true | 1 ;
一般情况下, typescript
默认认为对象的属性是可以改变的,但在如下情况会出现问题
1 2 3 4 function handle (method: "GET" | "POST" ) {}const obj = { method : "GET" };handle (obj.method );
因为默认推断了 obj.method
为 string
类型,然而 string
类型是有可能不属于 'GET' | 'POST'
类型的,因此报错 解决方法如下
1 2 3 4 const obj = { method : "GET" } as const ;const obj = <const >{ method : "GET" };
bigint 指非常大的整数,使用时需将 target
设置为 es2020
1 const bigintNum : bigint = 100n ;
never never 指为不存在的类型,永远不会被观察到的值 ,它是所有类型的子类型 ,用集合来解释即:never 是空集
例如下情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 type Circle = { kind : "circle" ; }; type Square = { kind : "square" ; }; type Sharp = Circle | Square ;function sharpHandle (sharp: Sharp ) { switch (sharp.kind ) { case "circle" : return sharp; case "square" : return sharp; default : return sharp; } }
当一个函数抛出一个异常、中止程序执行 或 死循环 ,返回值的类型就是 never
,也用于联合类型没有匹配到任何东西 的情况下的值类型
void 在 ts 中,一个不返回任何值的函数将自动推断返回值为 void
类型 而在 js 中,这种函数的返回值默认为 undefined
ts 中的 void
和 undefined
是不一样的
object 该类型指的是任何不是基元 的值,其并不同于 {}
,也不同于 Object
基元: string number bigint boolean symbol null undefined
unknown 该类型代表任何值,和 any
类似,但更安全,因为对未知的 unknown
值做任何事都是不合法的
仅允许将 unknown
类型赋值给 any
或 unknown
类型
当使用 typeof
关键字对其进行类型提取时,与 any
的表现也不一样:
1 2 3 4 type Any = keyof any type Unknown = keyof unknown
unknown
与任何类型交叉 ,均返回对方类型 与除 any
外的任何类型联合 ,均返回 unknown
类型
*注:因为 any
与任何类型联合或与除 never
以外的类型交叉均返回 any
类型
Function 注意大写 ,该类型描述了诸如 bind call apply
以及其他存在于 js 中所有函数值的属性。
Function
类型的值总是可以被调用,调用的方法均返回 any
。因此,并不推荐使用 Function
去定义函数类型。
元组 js 中并不支持元组,而 ts 支持,格式:[typeA, typeB]
1 const normal : [number , string ] = [9 , "baka" ];
函数 定义方法 类型别名和接口定义函数的方法分别为
1 2 3 4 5 6 interface Handle { (arg : number ): boolean ; } type Handle = (arg: number ) => boolean ;
注:在函数类型的形参中,ts 仅限制了形参的最大数量 以及每个形参的类型 ,对形参的名字无限制,允许定义的形参少于类型中形参的个数
1 2 3 4 type Handle = (arg1: number , arg2: booleab ) => boolean ;const handle : Handle = (wdnmd : number ): boolean => true ;
函数签名 函数允许自带属性,类型定义方式如下
1 2 3 4 5 6 7 8 type Handle = { desc : string ; (arg : number ): boolean ; }; const handle : Handle = (wdnmd ) => wdnmd === 1 ;handle.desc = "" ;
调用签名 即为函数 的类型,如下表示:此函数有一个 string
类型的参数,无返回值的。且存在一个类型为 string
的属性 desc
1 2 3 4 5 6 7 8 interface IFunc { (wdnmd : string ): boolean ; desc : string ; } const handle : IFunc = (arg: string ) => !!arg;handle.desc = "函数的属性" ;
构造签名 即为 class 的类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class TsClass { private arg : string ; constructor (arg: string ) { this .arg = arg; } } interface ITsc { new (wdnmd : string ): TsClass } function getTsClass (tsc: ITsc ) { return new tsc ('wdnmd' ); }
若将形参 tsc
的类型修改为 TsClass
,则会出现以下错误
1 2 3 4 function getTsClass (tsc: TsClass ) { return new tsc ("wdnmd" ); }
可选参数 不建议在回调函数中定义可选参数
函数重载 提供 {}
表示对函数的实现,这里称其为为 实现函数 ,反之为 重载函数
实现函数虽然定义了参数可选,但那是为了兼容两个重载函数,调用时仍然只能使用两种重载的情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function overload (timeStamp: number ): Date ;function overload (d: number , m: number , y: number ): Date ;function overload (dOrTimeStamp: number , m?: number , y?: number ): Date { if (m && y) { return new Date (y, m, dOrTimeStamp); } else { return new Date (dOrTimeStamp); } } overload (1650819030751 );overload (25 , 4 , 2022 );overload (25 , 4 );
调用函数时,是看不到实现函数 的参数的,只能看到重载函数 的参数
1 2 3 4 function overload (field: string ): void ;function overload ( ) {}overload ("this is a field" );
实现函数若存在参数,则必须要兼容所有的重载函数的参数类型,返回值同样需要兼容
1 2 3 4 5 6 7 8 function overload (field: string ): string ;function overload (field: number ): number ;function overload (field: string | number ): string | number { return "field" ; } overload ("this is a field" );
可以的话,更推荐用 联合类型的参数
而不是重载参数,因为 重载参数 不能使用两者均有可能的值作为参数。
1 2 3 4 5 6 7 function overload (field: string ): void ;function overload (field: number ): void ;const field : string | number = Math .random () > 0.5 ? "" : 0 ;overload (field);
this 入参 js 中不支持 this 作为函数参数,但 ts 允许这样做。 ts 允许在函数入参列表中第一个位置 处,手动写入this
并标记其类型。它仅仅是一个形式上的参数,并不会在编译后的代码中出现,仅仅是为了提供给 Typescript 编译器作为检查静态类型使用。
通常我们会在需要纠正 this 类型的场合下使用这个机制,参考如下示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Handle { public value : string ; constructor (value: string ) { this .value = value } onClick (this : Handle, e: Event ) { this .value = e.type ; } } interface myElement { addClickListener (onClick : (this : void , e: Event ) => void ): void }; const ele : myElement = { addClickListener (onClick: (this : void , e: Event) => void ) { onClick ({ type : 'click' } as Event ) } }; ele.addClickListener (new Handle ("default" ).onClick );
由于众所周知的 this 指向问题,上面代码中在调用 onClick 时,this 指向 undefined
,而在 onClick 函数中期望的 this 则是指向 Handle
类。经过限制后 ts 捕获到了错误并作出了提醒。若不做限制(两处有任意一处未对 this 进行定义),则会出现 Cannot set property 'value' of undefined
报错。
参数展开 ts 可以给 es6
中的函数形参展开式定义类型
1 2 3 4 5 6 function mutiply (n: string , ...m: number [] ) { return m.map ((x ) => n.padEnd (x, n)); } mutiply ("wdnmd" , 6 , 7 , 8 , 9 , 10 );
同理,也可以展开数组实参传入方法,如下:
1 2 3 const list : string [] = [];const list2 : string [] = ["a" , "b" ];list.push (...list2);
参数解构 1 2 3 4 5 6 7 8 9 10 type Parm = { label : string ; value : number ; }; function handle ({ label, value }: Parm ) { return `${label} : ${value} ` ; } handle ({ label : "baka" , value : 9 });
函数的 void 返回类型 当把函数类型定义为 返回 void 的函数
时,函数内允许返回任意类型 ,ts 会将其忽略,执行函数得到的结果被 ts 判断为 void
类型
1 2 3 4 5 6 7 8 type VoidFun = () => void ;const v1 : VoidFun = () => true ;const res = v1 ();console .log (res);
但若直接显式 的给函数定义返回值类型为 void
,那么这个函数必须不能返回任何值
1 2 const v2 = (): void => true ;
对象 定义方法 常见有下面几种定义方法
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 const obj : { label : string ; value : number } = { label : "baka" , value : 9 , }; interface INormal { label : string ; value : number ; } const obj : INormal = { label : "baka" , value : 9 , }; type Normal = { label : string ; value : number ; }; const obj : Normal = { label : "baka" , value : 9 , };
属性修饰符 可选属性 1 2 3 4 interface INormal { label?: string ; value : number ; }
只读属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 interface INormal { readonly label : string ; value : number ; } interface INormal { readonly value : { title : string ; count : number ; }; } obj.value .count = 99 ;
索引签名 格式为 { [key: KeyType]: ValueType }
其中 KeyType
仅能使用 number
、string
、symbol
和 模板字符串类型(类型章节有讲)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 interface INormal { [key : string ]: number ; } interface INormal { [key : string ]: number ; title : number ; } interface INormal { readonly [key : string ]: number ; }
与 Record
区别,Record 的 key 支持使用 字符串字面量类型与联合类型
类型扩展 接口 使用 extends
关键字对接口进行扩展,在后面继承 部分会详细说明
接口和类型别名可以互相继承
1 2 3 4 5 6 7 8 9 interface c extends a { sex : string ; } interface d extends a, b { address : string ; }
类型别名 使用交叉类型 对类型别名进行扩展,交叉类型会在后面详细说明
接口和类型别名可以互相交叉
1 2 3 4 5 6 7 8 type c = a & { sex : string ; }; type d = a & b & { address : string ; };
向现有类型添加字段 类型别名一经创建无法更改,无法添加字段
1 2 3 4 5 6 7 interface a { name : string ; } interface a { age : number ; }
类型推断 三元表达式 ts 会根据三元表达式冒号两侧自动推断类型
1 2 const string = Math .radom () > 0.5 ? 10 : "string" ;
函数返回值 变量经过函数内处理后返回,会自动进行类型缩小
1 2 3 4 5 6 function handle ( ) { let x : number | string | boolean ; x = Math .random () < 0.5 ? 10 : "string" ; return x; }
类型断言 两种断言方法
1 2 let str = "string" as any ;let str = <any >"string" ;
typescript
只允许类型断言转换为更具体 或不太具体 的类型,如不允许将 number
类型断言为 string
当不知道类型时,断言为 unknown
语义上比 any
更确切
类型谓词 is 对函数返回值定义类型 fieldName is Type
,当函数返回 true
时,变量会被指定为对应的类型 若不使用 is
,函数会被认为返回值类型是简单的 boolean
,变量依然是联合类型,无法确切定义具体类型
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 type bird = { fly : () => void ; }; type fish = { swim : () => void ; }; function isBird (pet: bird | fish ): pet is bird { return (<bird>pet).fly !== undefined ; } function getPet ( ): bird | fish { const fish : fish = { swim : () => {}, }; const bird : bird = { fly : () => {}, }; return Math .random () < 0.5 ? fish : bird; } const pet = getPet ();isBird (pet) ? pet.fly () : pet.swim ();
也可以用来过滤数组
1 2 3 4 5 const zoos : Array <fish | bird> = [getPet (), getPet (), getPet ()];const fishs = zoos.filter (isFish);const fishs1 = zoos.filter ((pet): pet is fish => isFish (pet));
关键字 keyof 提取一个类型的所有键值组成一个新的类型
1 2 3 4 5 6 7 type Normal = { label : string ; value : number ; }; type KeysNormal = keyof Normal ;
示例:获取一个类型的所有值的类型
1 2 3 4 5 6 type Normal = { label : string value : number } type Values = Normal [keyof Normal ]
示例:为获取对象属性的方法定义类型
1 2 3 function getProperty<T extends object , K extends keyof T>(obj : T, key : K): T[keyof T] { return obj[key] }
当 keyof
用于基本类型 时,将会提取该类型的所有可用方法
1 2 3 4 type Num = keyof number type Bool = keyof boolean
keyof
也可以用于 class,此时不会获取 class 中的 private
和 protected
属性
1 2 3 4 5 6 7 8 9 10 11 12 13 class Base { private label : string public value : number constructor (label: string , value: number ) { this .label = label this .value = value } getValue ( ) { return this .value } } type Normal = keyof Base
keyof
可以获取枚举类型,但直接使用会获得枚举属性的方法,想获取她的键值,需要结合 typeof
使用
1 2 3 4 5 6 7 8 enum HttpMethod { Get , Post } type FakeMethod = keyof HttpMethod type Method = keyof typeof HttpMethod
对索引签名使用 1 2 3 4 5 6 7 interface normal { [fieldName : string ]: number ; } type KeysNormal = keyof normal;
对类使用 1 2 3 4 5 6 7 8 9 10 11 12 13 class Normal { value : string ; constructor (value: string ) { this .value = value; } nFun ( ) { return "normal" ; } } type KeysNormal = keyof Normal ;
typeof 提取一个变量的类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function normal (arg: string ): boolean { return !!arg; } type Normal = typeof normal;const obj = { label : "baka" , value : 9 , }; type Obj = typeof obj;
提取 any 和 unknown 类型 对 any
和 unknown
进行类型提取时则有比较特殊的结果
1 2 3 4 type Any = keyof any type Unknown = keyof unknown
infer infer
关键字用于声明类型变量,以存储在模式匹配过程中所捕获的类型
下面这个示例就捕获到了 number[]
中的 number
类型
1 2 3 type Normal <T> = T extends (infer U)[] ? U : T;type T0 = Normal <number []>;
声明两个类型变量,捕获对象中的类型
1 2 3 4 5 6 7 8 type Normal <T> = T extends { label : infer U; value : infer R } ? [U, R] : T;type Base = { label : string ; value : number ; }; type T0 = Normal <Base >;
infer 使用注意:
仅可以在条件类型 的 extends
子句中使用
infer
声明的类型变量 仅可以在条件类型的 true
分支使用
协变与逆变 改编上面示例,两个 infer 均指向同一个变量,结果为联合类型 (协变位置)
1 2 3 4 5 6 7 8 type Normal <T> = T extends { label : infer U; value : infer U } ? U : T;type Base = { label : string ; value : number ; }; type T0 = Normal <Base >;
再做改编,将 infer 指向函数形参,结果为交叉类型 (逆变位置)
1 2 3 4 5 6 7 8 9 10 11 12 13 type Base = { label : (arg: string ) => void ; value : (arg: number ) => void ; }; type Normal <T> = T extends { label : (arg: infer U ) => void ; value : (arg: infer U ) => void ; } ? U : T; type T0 = Normal <Base >;
函数参数 是逆变 的,而对象属性 是协变 的。 在逆变位置 的同一类型变量中的多个候选会被推断成交叉类型 。
类 基本结构 1 2 3 4 5 6 7 8 class Normal { label : string ; value : number ; constructor ( ) { this .label = "" ; this .value = 0 ; } }
构造函数 构造函数在执行 new 创建对象时执行,内部允许初始化类的成员属性
1 2 3 4 5 6 7 8 9 class Normal { label : string ; constructor (label: string ) { this .label = label; } } console .log (new Normal ("baka" ).label );
有两点要注意:
构造函数不能有类型参数
构造函数不能有返回类型注释
允许对构造函数使用参数重载
1 2 3 4 5 6 7 8 class Normal { label : string ; constructor (name: string , age: number ); constructor (sex: boolean ); constructor (ns: string | boolean , age?: number ) { } }
参数属性 即在构造函数 constructor
参数上定义类成员,由 readonly、public、protected、private
等修饰
1 2 3 4 5 6 7 8 9 10 class Normal { constructor (public label: string ) {} echo ( ) { console .log (this .label ); } } new Normal ("baka" ).echo ();
Getters/Setters 为两组方法,格式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Normal { label : string = "" ; get title () { return this .label ; } set title (title ) { this .label = title; } } const normal = new Normal ();const title = normal.title ;normal.title = "baka" ;
存在以下几点特性:
如果存在 getter 但不存在 setter,该属性自动变为只读属性
如果没有指定 setter 参数的类型,会自动从 getter 的返回值类型推断出来
getter 和 setter 必须设置相同的成员可见性
setter 的参数类型 Typescript 4.3 后,允许 setter 的参数类型为 getter 返回值的子类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Normal { label : string = "" ; get title () { return this .label ; } set title (value: string | number ) { if (typeof value === "string" ) { this .label = value; } } } const normal = new Normal ();normal.title = 114514 ;
索引签名 一旦限制索引签名,类中的属性和方法都将受到约束
1 2 3 4 5 6 7 8 9 class Normal { [fieldName : string ]: boolean | ((...args: string [] ) => string ); label : boolean = true ; handle (arg: string ) { return arg; } }
implements 实现一个接口或类型别名,当一个类指定 implements
后,必须实现其中的所有必须属性。格式如下:
1 2 3 4 5 6 7 interface Normal { label : string ; } class NormalClass implements Normal { label = "baka" ; }
一个类可以实现多个 implements
1 2 3 4 5 6 7 8 9 10 11 12 interface Normal { label : string ; } interface Hard { value : number ; } class NormalClass implements Normal , Hard { label = "baka" ; value = 9 ; }
implement
仅对 class
的成员进行限制,并不会给改变类的类型
1 2 3 4 5 6 7 8 9 10 11 interface Normal { label : string ; value?: number ; } class NormalClass implements Normal { label = "baka" ; } new NormalClass ().value ;
extends (派生类)继承另一个类(基类),获取其所有非 private
修饰的属性 。
当一个类继承另一个类时,若该类存在 constructor
构造函数,则必须指定 super
调用。
若派生类未显示写明构造函数,则默认使用 基类 的构造函数。
super: 调用父类的构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 class Base { label : string = "baka" ; } class Normal extends Base { constructor ( ) { super (); } } console .log (new Normal ().label );
派生类为基类的子类型,允许被赋值
1 const base : Base = new Normal ();
重写基类方法 派生类允许使用和基类同名的方法,来对其进行重写
但必须要与基类方法兼容,如参数 以及返回值类型 ,类似函数重载(属性也是如此)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Base { echo (): void { console .log ("baka" ); } } class Normal extends Base { echo (message?: string ): void { if (!message) { super .echo (); } else { console .log (message); } } }
初始化顺序 当派生类使用 new
关键字创建对象时,内部按照以下顺序执行
字段初始化 (基类)
构造函数运行 (基类)
字段初始化 (派生类)
构造函数运行 (派生类)
es5 继承内置对象问题 当 typescript.json
中的 target
设置为 es5
时,使用 extends
继承内置对象 时会出现一些问题
1 2 3 4 5 6 7 8 9 10 class BakaError extends Error { constructor (msg: string ) { super (msg); } echo ( ) { return `代码出错了笨蛋!: ${this .message} ` ; } } const bakaError = new BakaError ("abaaba" );
编译未报错,运行如下代码,发现运行报错
1 2 3 4 console .log (bakaError.echo ());console .log (bakaError instanceof BakaError );
原因:由于 es5 并没有 class 这种玩意儿,需要手动设置一下原型对象
对 constructor 进行如下修改
1 2 3 4 5 6 7 class BakaError extends Error { constructor (msg: string ) { super (msg); (<any >Object ).setPrototypeOf (this , BakaError .prototype ); } }
再次执行,正常运行
1 2 3 4 console .log (bakaError.echo ());console .log (bakaError instanceof BakaError );
类中的 this 由于 js 遗留问题,类方法内的 this 可能会存在乱指向的问题, 比如下面示例
1 2 3 4 5 6 7 8 9 10 11 12 class Normal { label = "baka" ; getLabel ( ) { return this .label ; } } const obj = { label : "baka is you" , getLabel : new Normal ().getLabel , }; console .log (obj.getLabel ());
改编为 箭头函数 即可解决这种情况
1 2 3 4 5 6 7 8 9 10 11 12 class Normal { label = "baka" ; getLabel = () => { return this .label ; }; } const obj = { label : "baka is you" , getLabel : new Normal ().getLabel , }; console .log (obj.getLabel ());
注:改编为箭头函数后,派生类无法再使用 super
调用该方法。 typescript
并不会报错,但在运行时将会抛出错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Base { getLabel = () => { console .log ("baka" ); }; } class Normal extends Base { constructor ( ) { super (); super .getLabel (); } } new Normal ();
修饰符 readonly 被修饰的属性不允许在构造函数以外 的地方进行修改
1 2 3 4 5 6 7 8 9 10 class Normal { readonly label : string ; constructor ( ) { this .label = "" ; } test ( ) { this .label = "?" ; } }
public 表示类成员在任何地方都可以访问,是默认值 ,不指定时默认为 public
。
protected protected
修饰的成员只对当前类 和子类 可见。
1 2 3 4 5 6 7 8 9 10 11 12 13 class Base { protected label : string = "baka" ; } class Normal extends Base { getLabel ( ) { console .log (this .label ); } } new Normal ().label ;
派生类 可以暴露基类中 protocted
所修饰的成员。
1 2 3 4 5 6 7 8 9 10 class Base { protected label : string = "baka" ; } class Normal extends Base { public label : string = "baka is you" ; } new Normal ().label ;
private private
修饰的成员只对当前类 可见。
static static
修饰的成员为静态成员,访问其不需要实例化,直接用类名 就可以访问
1 2 3 4 5 class Base { static label : string = "baka" ; } Base .label ;
静态成员允许被继承,且可以被派生类 直接用类名 访问
1 2 3 4 5 6 7 class Base { static label : string = "baka" ; } class Normal extends Base {}Normal .label ;
es2015 以上支持使用 专用标识符 #
修饰静态成员,如下例的 #label
。 修饰后的成员变为私有属性,仅允许类内部 通过类名 调用。
且 static
支持修饰一个代码块,称为 static 区块 ,区块内同样可以访问 #
静态成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Normal { static #label = "baka" ; get label () { return Normal .#label; } static { this .#label = "baka is you" ; } } console .log (new Normal ().label );Normal .#label;new Normal ().#label;
在泛型类中,static
修饰的成员是不能使用 类型参数 的
1 2 3 4 class Normal <T> { static label : T; }
类表达式 类允许像方法一样用表达式声明一个匿名类 并赋值,同样允许使用泛型
1 2 3 4 5 const Normal = class <T> { constructor (public label: T ) {} }; console .log (new Normal <string >("baka" ).label );
this 类型 在类中可以使用 this
类型来指向当前 this
所指向的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 class Base { label : string = "baka" ; echo (other: this ) { return this .label ; } } class Normal extends Base { value : number = 9 ; } new Normal ().echo (new Base ());
this is Type 在类和接口的方法的返回值 的位置使用,缩小调用该方法的目标的类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Normal <T> { public label?: T; hasLabel (): this is { label : T } { return !!this .label ; } } const normal = new Normal <string >();normal.label = "baka" ; if (normal.hasLabel ()) { normal.label ; }
抽象类与抽象成员 使用 abstract
修饰后即变为抽象类,抽象类不能被实例化,只能被作为基类继承。
使用 abstract
修饰的成员为抽象成员,abstract
关键字于 public
readonly
等之后使用,仅允许出现在抽象类 中,不可以实现,子类继承后必须实现该成员。
1 2 3 4 5 6 7 8 9 10 11 abstract class Base { public abstract readonly label : string ; public value : number = 9 ; } class Normal extends Base { public label : string = "baka" ; }
类之间的细节补充 当两个类的成员 和类型 完全相同时,是可以互相替代的
1 2 3 4 5 6 7 8 9 10 11 class Normal { label : string = "baka" ; value : number = 9 ; } class Hard { label : string = "baka is you" ; value : number = 6 ; } const normal : Normal = new Hard ();
当两个类的成员存在兼容关系但未显式继承时,也是存在隐式继承的
1 2 3 4 5 6 7 8 9 10 class Normal { label : string = "baka" ; } class Hard { label : string = "baka is you" ; value : number = 6 ; } const normal : Normal = new Hard ();
针对空类时,任何类型都可以是它的实例对象
1 2 3 4 5 6 class Empty {}const a : Empty = "baka" ;const b : Empty = 9 ;const c : Empty = {};const d : Empty = () => {};
泛型 定义泛型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type Normal <T> = { value : T; }; interface Normal <T> { value : T; } type Normal <T> = (arg: T ) => T;interface Normal <T> { (arg : T): T; } interface NormalFun { <T>(ary : T): T; }
自动推断 泛型是具备自动推断的,下面的示例自动将 T 推断为了 number
1 2 3 4 5 6 7 function genericsFunc<T>(tList : T[]): T | undefined { return tList[0 ]; } const num = genericsFunc ([1 , 2 , 3 ]);
定义多个泛型别名 1 2 3 4 5 6 7 8 9 function genericsFunc<I, O>(arr : I[], func : (arg: I[] ) => O): O[] { const output = func (arr); return [output]; } const output = genericsFunc ([1 , 3 ], (arg : number []): string => { return arg.toString (); });
指定类型参数 有时候通过自动推断并不能实现预期效果,这时候就要手动添加类型
1 2 3 4 function combine<T>(arr1 : T[], arr2 : T[]): T[] { return arr1.concat (arr2); } const arr = combine<number | string >([1 , 2 ], ["a" , "b" ]);
泛型类 1 2 3 4 5 6 7 8 class Normal <T> { value : T; constructor (value: T ) { this .value = value; } } new Normal <number >(2 );
泛型约束 有时需要使泛型变量受到某些约束,例如必须存在 length
属性,就需要对泛型进行限制
1 2 3 4 5 6 7 8 function checkLonger<T extends { length : number }>(a : T, b : T): T { return a.length > b.length ? a : b; } const longer = checkLonger ("wdnmd" , "nmdwsm" );
在泛型约束中使用类型参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 interface INormalArg { label : string ; value : number ; } function normal<T, K extends keyof T>(arg : T, key : K): T[K] { return arg[key]; } const arg : INormalArg = { label : "baka" , value : 9 , }; const value = normal (arg, "value" );
对 class 类型进行泛型约束 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Normal { nFun ( ) { return "normal" ; } } class Special extends Normal { sFun ( ) { return "special" ; } } function handle<F extends Normal >(c : new () => F): F { return new c (); } const spe = handle (Special );console .log (spe.nFun (), spe.sFun ());
编写通用函数准则
可以的话,使用类型本身,而不是对其约束
尽可能少的使用指定类型参数
如果一个类型参数只出现在一个地方,则需要考虑是否真的需要它
其他类型 索引访问类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 type Normal = { label : string ; value : number ; }; interface INormal { label : string ; value : number ; } type label = Normal ["label" ];type value = INormal ["value" ];
结合 keyof
使用,获取接口或类型别名的所有类型
1 2 3 4 5 6 7 type Normal = { label : string ; value : number ; }; type NormalValue = Normal [keyof Normal ];
结合 typeof
使用,通过特殊类型索引 number
, 获取数组子项类型
*原因:数组的默认属性类型即为 number
1 2 3 4 5 6 7 8 9 const normalArray = [ { label : "baka" , value : 9 , }, ]; type Normal = typeof normalArray[number ];
交叉类型 格式,其中 &
为交叉运算符 :
在 typescript 中,可以把类型理解成一系列值的集合,例如:
number :所有数字的集合对象类型 :内部所有属性的集合
而交叉类型 a & b
的结果正式两个集合的交集 当两个类型不存在交集时,得到结果 never
交叉运算符 对于交叉运算符,其满足以下几个特点:
唯一性: A & A 等价于 A
满足交换律: A & B 等价于 B & A
满足结合律: A & B & C 等价于 A & ( b & c )
父类型收敛: 若 B 是 A 的子类型,则 A & B 将被收敛为 B
父类型收敛的几个例子:
1 2 3 4 5 6 type A0 = 1 & number ;type A1 = "1" & string ;type A2 = true & boolean ;
特殊: 除了 never
类型外, any
类型与任何类型交叉均为 any
类型
对象类型的交叉 因为需要使得交叉结果均可继承自交叉双方的类型,所以当对象交叉时,结果是这样的
1 2 3 4 5 6 7 8 type Base1 = { label : string } interface Base2 { value : number } type Normal = Base1 & Base2
当两个对象类型存在相同属性但类型不同时,将会对该属性进行交叉
1 2 3 4 5 6 7 8 9 type Base1 = { label : string } interface Base2 { label : number } type Normal = Base1 & Base2
对象类型交叉时尤其注意一点:当类型指定或被推断为具体的字面量类型 时,交叉结果将为 never
1 2 3 4 5 6 7 8 9 type Base1 = { label : 1 } interface Base2 { value : 2 } type Normal = Base1 & Base2
特殊:暂未搞懂原因:
当两个对象中存在共有属性,其中一个为 联合类型 且交叉无交集时,交叉后的类型将直接变为 never
类型,其他元素也会消失
其中:由于 boolean
可以看作字面量 true 和 false 的联合类型,因此也拥有上述特性
1 2 3 4 5 6 7 8 type Base1 = { label : string } interface Base2 { label : 1 | 2 } type Normal = Base1 & Base2
函数类型的交叉 typescript 会利用函数重载的特性来实现不同函数的交叉运算
因此可能会出现如下这种情况
1 2 3 4 5 6 7 8 9 10 11 12 type F1 = (arg1: string , arg2: string ) => void type F2 = (arg1: number , arg2: number ) => void type F3 = F1 & F2 const fn : F3 = (a, b ) => {}fn ('baka' , 9 )
且函数类型交叉运算后,相同参数变为联合类型,而返回值类型 将做交叉运算
因此将会出现下面这张情况
1 2 3 4 5 6 7 8 type F1 = (arg1: string , arg2: string ) => string type F2 = (arg1: number , arg2: number ) => number type F3 = F1 & F2 const fn : F3 = (a, b ) => { return '1' as never }
映射类型 1 type Mapped <K> = { [ P in K ]: T }
P in K
类似 js 中的 for in
语句,用于遍历 K 中的所有类型,T
为你希望该属性所表示的任意类型
添加修饰符 允许使用修饰符,在修饰符前面添加 +
或 -
表示 添加/移除 修饰符,默认为 +
使用 readonly
和 ?
修饰符:
1 2 3 4 type Normal = { readonly [ K in "x" | "y" ]?: number }type ReadonlyAndRequired <T> = { readonly [ K in keyof T ]-?: T[K] }
as 重新映射 ts 4.1
开始支持,允许对键值重新映射,格式如下
1 type KeyRemapping <T> = [ K in keyof T as NewKeyType ]: T[K]
NewKeyType
必须为 string | number | symbol
的子类型
在对键进行重新映射时,若 as
子句返回 never
类型,该键将被删除
示例:将普通接口类型映射为 getter
格式的类型,即 getXxx: () => Type
:
1 2 3 4 type Getters <T> = { [K in keyof T as `get${Capitalize<string & K>} ` ]: () => T[K] };type Normal = Getters <{ value : number }>;
其中:
`` 为模板字面量类型,后面将会说明
Capitalize:将 string
类型转换为首字母大写
string & K:利用交叉类型返回 never
,过滤非 string
类型
条件类型 类似 js 中的 三元表达式
格式
1 type Type = T extends U ? X : Y;
结合泛型使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type Normal = { label : string ; }; type Hard = { value : number ; }; type Level <T extends number | string > = T extends number ? Normal : Hard ;function getLevel<T extends number | string >(params : T): Level <T> { throw "" ; } const level1 = getLevel (9 );const level2 = getLevel ("" );const level3 = getLevel (Math .random () > 0.5 ? 9 : "" );
结合 infer 关键字实现类型分发 下面这个案例封装了一个获取函数类型返回值的类型
1 2 3 4 5 6 7 8 type GetReturnType <T extends (...args : any []) => any > = T extends ( ...args : any [] ) => infer R ? R : any ; type Normal = GetReturnType <() => string >;
其实这就是预定义类型 ReturnType
的实现方法
分布式条件类型 如果被检查的类型,即 T extends U ? X : Y
中的 T
是一个裸类型参数 ,则该条件类型为分布式条件类型
裸类型参数 :没有被数组(T[]
)、元组([T]
)或Promise(Promise<T>
)包装过
对于分布式条件类型,若传入的被检查 的参数为联合类型 时,在运算过程中将被分解为多个分支
1 2 3 type Normal = A | B | C extends U ? X : Ytype Normal = (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
下面展示了是否是分布式条件类型时,传入的参数得到的最终结果的不同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type Normal <T> = T extends boolean ? "Y" : "N" type t0 = Normal <boolean | number >type WarppedTuple <T> = [T] extends [boolean ] ? "Y" : "N" type WarppedArrary <T> = T[] extends boolean [] ? "Y" : "N" type WarppedPromise <T> = Promise <T> extends Promise <boolean > ? "Y" : "N" type T1 = WarppedTuple <boolean | number >type T2 = WarppedArrary <boolean | number >type T3 = WarppedPromise <boolean | number >
模板字面量类型 ts 4.1 开始支持,写法类似 js 中的模板字符串,格式如下
其中类型占位符 T
允许为 string | number | boolean | bigint
的子类型
使用示例:
1 2 3 4 type Base <T extends string > = `chiruno is ${ T } ` type Normal = Base <"baka" >
当类型占位符 T 为联合类型 时,联合类型会被自动展开 而对于包含多个类型占位符 的情况时,多个联合类型将被展开并解析为叉积
1 2 3 4 5 6 7 8 9 type Direction = "left" | "top" | "right" | "bottom" ;type CssPadding = `padding-${Direction} ` type Concat <T extends string , K extends string > = `${T} -${K} ` type Hard = Concat <"top" | "bottom" , "true" | "false" >
模板字面量类型可以将非字符串类型的基本类型字面量 转换为对应的字符串类型字面量
1 2 3 4 type ToString <T extends number | string | boolean | bigint > = `${T} ` type Normal = ToString <9 | "baka" | true | -1234n >
结合 infer
和 条件类型 进行推断
1 2 3 4 5 type Direction = "left" | "top" | "right" | "bottom" ;type Prefix <T> = T extends `${ infer R } -${ Direction } ` ? R : T;type Normal = Prefix <"margin-left" >
预定义类型 ReturnType 使用方式:ReturnType<T>
,T 为一个函数类型
获得一个函数类型的返回值类型
1 2 3 4 type Normal = (arg: string ) => boolean ;type Ret = ReturnType <Normal >;
Uppercase & Lowercase & Capitalize & Uncapitalize 分别为 全部大写 、全部小写 、首字母大写 、首字母小写 , 仅对 string
类型进行处理的内置类型
1 2 type Normal = Uppercase <"baka" >
声明文件 npm 包的声明文件 与 npm 包绑定的声明文件
在两种情况下,声明文件会和 npm 包绑定
在包目录下存在类型文件 index.d.ts
package.json
中显式的定义了 types
属性,指向具体位置的声明文件,如 "types": "index.d.ts"
第三方声明文件
有些时候,npm 包维护者并没有提供声明文件,而由社区中其他人向 @types
贡献了声明文件,可以尝试安装一下对应的 @types
包
1 npm i @types/[package name] --save-dev
默认情况下,typescript 会自动包含支持全局使用的任何声明定义,如安装 @types/node
后可以在全局使用 process
。 但如果你不希望被各种全局变量污染作用域,可以在 tsconfig.json
中作如下配置。
1 2 3 4 5 { "compilerOptions" : { "types" : [ "node" ] } }
像这样配置了 types 以后,将只允许使用 node 的 @types
包,即使安装了其他的 @types
包,也不会生效,除非同样将其加入到这里。
自行定义声明文件
若该 npm 包连第三方声明文件都不存在,这时候就只能自行定义声明文件了
创建一个 types
目录,将 marry
的声明文件放到其中,目录为 types/marry/index.d.ts
。并配置 tsconfig.json
中的 baseUrl
和 path
字段,如下。
1 2 3 4 5 6 7 8 9 { "compilerOptions" : { "module" : "commonjs" , "baseUrl" : "./" , "paths" : { "*" : [ "types/*" ] } } }