类型分类 聊聊TS
中的类型分类吧
原始类型
JavaScript 中以下类型被视为原始类型:string
、boolean
、number
、bigint
、symbol
、null
和 undefined
。
1 2 3 4 5 6 7 8 9 10 let str : string = "SakuraSnow" ;let num : number = 16 ;let bool : boolean = true ;let u : undefined = undefined ;let n : null = null ;let big : bigint = 100n ;let sym : symbol = Symbol ("snow" );
对象类型 对象 1 2 3 4 5 6 7 8 9 10 11 type User = { name : string , age : number } let user : User = { name : "Sakura" , age : 16 }
注意,object
和Object
和空对象{}
是不同的
小object
代表的是==所有非原始类型==,也就是说我们不能把 number
、string
、boolean
、symbol
等 原始类型赋值给 object
。在严格模式下,null
和 undefined
类型也不能赋给 object
。
大Object
代表所有拥有 ==toString,hasOwnProperty 方法的类型==,所以所有原始类型、非原始类型都可以赋给 Object
。同样,在严格模式下,null
和undefined
类型也不能赋给Object
。
{}空对象
类型和大 Object
一样,也是表示原始类型和非原始类型的集合,并且在严格模式下,null
和 undefined
也不能赋给{}
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 let lowerCaseObject : object ;lowerCaseObject = 1 ; lowerCaseObject = 'a' ; lowerCaseObject = true ; lowerCaseObject = null ; lowerCaseObject = undefined ; lowerCaseObject = {}; let upperCaseObject : Object ;upperCaseObject = 1 ; upperCaseObject = 'a' ; upperCaseObject = true ; upperCaseObject = null ; upperCaseObject = undefined ; upperCaseObject = {}; let ObjectLiteral : {};ObjectLiteral = 1 ; ObjectLiteral = 'a' ; ObjectLiteral = true ; ObjectLiteral = null ; ObjectLiteral = undefined ; ObjectLiteral = {};
综上结论:{}、大 Object 是比小 object 更宽泛的类型(least specific),{} 和大 Object 可以互相代替,用来表示原始类型(null、undefined 除外)和非原始类型;而小 object 则表示非原始类型。
从上面示例可以看到,大 Object
包含原始类型,小 object
仅包含非原始类型,所以大 Object
似乎是小 object
的父类型。实际上,大 Object
不仅是小 object
的父类型,同时也是小 object
的子类型。
1 2 3 4 type isLowerCaseObjectExtendsUpperCaseObject = object extends Object ? true : false ; type isUpperCaseObjectExtendsLowerCaseObject = Object extends object ? true : false ; upperCaseObject = lowerCaseObject; lowerCaseObject = upperCaseObject;
为什么这么设置呢,我也在想,等我想到再补充
数组 1 2 3 4 let arr : string [] = ["1" ,"2" ];let arr : Array <string > = ["1" ,"2" ];let arr : (number | string )[] = ["1" , 2 ];
函数 函数定义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 function sum (x: number , y: number ): number { return x + y; } let sum : (x: number , y: number ) => number = function (x: number , y: number ): number { return x + y; }; interface SumFunc { (x : number , y : number ): number ; }
函数参数 1 2 3 4 5 6 7 8 function buildName (firstName: string , lastName?: string ) { return lastName ? `${firstName} ${lastName} ` : firstName; } buildName ('Sakura' , 'Snow' );buildName ('Sakura' );
1 2 3 4 function buildName (firstName: string , lastName: string = "Snow" ) { return `${firstName} ${lastName} ` ; }
1 2 3 4 5 6 7 8 function push<T>(array : Array <T>, ...items : Array <T>): Array <T> { items.forEach ((item: T ) => array.push (item)); return array; } let a : Array <number > = [];push<number >(a, 1 , 2 , 3 );
函数重载
函数重载或方法重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function add (x: number , y: number ): number ;function add (x: string , y: string ): string ;function add (x, y ) { return x + y; } let num : number = add (1 ,2 );let str : string = add ("Sakura" , "Snow" );let count : number = add ("Sakura" , "Snow" );
元组
众所周知,数组一般由同种类型的值组成,但有时我们需要在单个变量中存储不同类型的值,这时候我们就可以使用元组。在 JavaScript 中是没有元组的,元组是 TypeScript 中特有的类型,其工作方式类似于数组。
元组最重要的特性是可以限制==数组元素的个数和类型==,它特别适合用来实现多值返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type Info = [string , number ];let info : Info = ["Sakura" , 16 ]; let info : Info = ["Sakura" ]; type Tuple = [string , number ?];let tuple : Tuple = ["Sakura" , 16 ]; let tuple : Tuple = ["Sakura" ]; let tuple : Tuple = [16 ]; type Point = [number , number ?, number ?];const x : Point = [10 ]; const xy : Point = [10 , 20 ]; const xyz : Point = [10 , 20 , 10 ]; type RestTupleType = [number , ...string []];let restTuple : RestTupleType = [666 , "Semlinker" , "Kakuqo" , "Lolo" ];
其他类型 void void
表示没有任何类型,和其他类型是平等关系,不能直接赋值
1 2 let a : void ; let b : number = a;
你只能为它赋予null
和undefined
(在strictNullChecks
未指定为true时)。声明一个void
类型的变量没有什么大用,我们一般也只有在函数没有返回值时去声明。
1 2 3 4 function fun ( ): void { console .log ("this is TypeScript" ); }
never 相比于void
表示没有,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 24 25 26 27 function error (message: string ): never { throw new Error (message); } function fail ( ) { return error ("Something failed" ); } function infiniteLoop ( ): never { while (true ) {} } function foo (data : number |string ) { if (typeof data === "number" ) { } else if (typeof data === "string" ) { } else { let rua = data; console .log (rua.name ) } }
never
类型同null
和undefined
一样,也是任何类型的子类型,也可以赋值给任何类型。
但是没有类型是never
的子类型或可以赋值给never
类型(除了never
本身之外),即使any
也不可以赋值给never
any 表示一个变量的值可以是任何类型,并且去掉类型检查。基本等价于变回js
了
在许多场景下,这太宽松了。使用 any
类型,可以很容易地编写类型正确但在运行时有问题的代码。如果我们使用 any
类型,就无法使用 TypeScript 提供的大量的保护机制。请记住,any 是魔鬼!
尽量不要用any。
1 2 3 4 5 6 function foo ( ) : any { return null ; } console .log (foo ().data );
unknown 表示一个变量的类型是未知的,每次使用前都要进行类型检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function foo ( ): string | number { if (Math .random () < 0.5 ) { return 0 ; } else { return "" } } let data : unknown = foo ();let num = 10 ;if (typeof data === "number" ) { let sum = num + data; console .log (sum); }
unknown
与any
一样,所有类型都可以分配给unknown
。
但是任何类型的值可以赋值给any
,同时any
类型的值也可以赋值给任何类型。unknown
任何类型的值都可以赋值给它,但它只能赋值给unknown
和any
包装类型
Number、String、Boolean、Symbol被称为包装类型,当然这是我自己起的名字
原始类型number
、string
、boolean
、symbol
混淆的首字母大写的 Number
、String
、Boolean
、Symbol
类型,后者是相应原始类型的包装对象
从类型兼容性上看,原始类型兼容对应的对象类型,反过来对象类型不兼容对应的原始类型。
1 2 3 4 let num : number ;let Num : Number ;Num = num; num = Num ;
字面量类型
在 TypeScript 中,字面量不仅可以表示值,还可以表示类型,即所谓的字面量类型。
目前,TypeScript 支持 3 种字面量类型:字符串字面量类型、数字字面量类型、布尔字面量类型,对应的字符串字面量、数字字面量、布尔字面量分别拥有与其值一样的字面量类型,具体示例如下。
1 2 3 4 5 6 7 8 9 let specifiedStr : 'this is string' = 'this is string' ; let specifiedNum : 1 = 1 ; let specifiedBoolean : true = true ; let str : string = 'any string' ;specifiedStr = str; str = specifiedStr;
当然单单使用一个字面量类型没啥意义,它真正的应用场景是可以把多个字面量类型组合成一个联合类型
1 2 let arr : Array <1 | 2 | 3 > = [1 , 2 , 3 ]
联合类型 联合类型表示取值可以为多种类型中的一种,使用 |
分隔每个类型。你可以把它当成是求交集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let myFavoriteNumbermyFavoriteNumber : string | number ;myFavoriteNumber = 'seven' ; myFavoriteNumber = 7 ; const sayHello = (name: string | undefined ) => { }; let num : 1 | 2 = 1 ;type EventNames = 'click' | 'scroll' | 'mousemove' ;let n!: number | string ;
交叉类型 交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性,使用&
定义交叉类型。
注意,如果我们仅仅把原始类型、字面量类型、函数类型等原子类型合并成交叉类型,是没有任何用处的,因为任何类型都不能满足同时属于多种原子类型,比如既是 string 类型又是 number 类型。因此,下面的代码中,类型别名 Useless 的类型是 never。
1 2 type Useless = string & number ;
交叉类型真正的用武之地就是将多个接口类型合并成一个类型 ,从而实现等同接口继承的效果,也就是所谓的合并接口类型,代码如下所示:
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 36 37 38 type IntersectionType = { id : number ; name : string ; } & { age : number };const mixed : IntersectionType = { id : 1 , name : 'name' , age : 18 } type Person = { name : string , age : number , opt : { walk : () => void ; } } type Animal = { name : number , age : number | string , opt : { eat : (food: string ) => void ; } } type Sth = Person & Animal ;let o : Sth = { name : null , age : 16 , opt : { walk ( ) {}, eat ( ) {}, } }
在上述示例中,我们通过交叉类型,使得IntersectionType
同时拥有了 id、name、age 所有属性,这里我们可以试着将合并接口类型理解为求并集。
枚举类型
枚举
通常用来约束某个变量的取值范围
解决了使用字面量
进行类型约束的问题
在类型约束位置会产生重复代码
逻辑含义和真实的值产生了混淆,会导致当修改真实值的时候,产生大量修改
字面量类型不会进入到编译结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 let gender : "男" | "女" ;gender = "女" ; gender = "男" ; function searchUsers (g:"男" | "女" ) {}type Gender = "帅哥" | "美女" ;let gender : Gender ;gender = "女" ; gender = "男" ; function searchUsers (g:Gender ) {}
这时候就可以使用枚举来优化
1 2 3 4 5 6 7 8 9 10 11 12 13 enum Gender { male = "美女" , female = "帅哥" } let gender : Gender ;gender = Gender .male ; gender = Gender .female ; function searchUser (g: Gender ) {}searchUser (gender);
类型推断
类型推断:在很多情况下,TypeScript 会根据上下文环境自动推断出变量的类型
初始化值的变量
、有默认值的函数参数
、函数返回的类型
都可以根据上下文推断出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 let str = 'this is string' ; let num = 1 ; let bool = true ; function add (a: number , b: number ) { return a + b; } const count = add (1 , 1 ); function add (a: number , b = 1 ) { return a + b; } const count1 = add (1 );const count2 = add (1 , '1' );
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any
类型而完全不被类型检查:
1 2 3 let myFavoriteNumber;myFavoriteNumber = 'seven' ; myFavoriteNumber = 7 ;
类型断言
所以有时候你会遇到这样的情况,你会比TypeScript
更了解某个值的详细信息。通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。
通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么” 。
类型断言不是类型转换,它不会真的影响到变量的类型。
有时会碰到我们比TypeScript
更清楚实际类型的情况,比如下面的例子:
1 2 const arrayNumber : number [] = [1 , 2 , 3 , 4 ];const greaterThan2 : number = arrayNumber.find (num => num > 2 );
其中,greaterThan2
一定是一个数字,因为 arrayNumber
中明显有大于 2 的成员,但静态类型对运行时的逻辑无能为力。
在 TypeScript
看来,greaterThan2
的类型既可能是数字,也可能是 undefined
,所以上面的示例中提示了一个 ts(2322)
错误,此时我们不能把类型 undefined
分配给类型 number
。
所以,这时候就需要类型断言了
强制类型断言 强制指定操作对象的类型
1 2 3 4 5 let n!: number | string ;let num : number = <number >n;let str : string = n as string ;
非空断言 使用!
断言操作对象是非 null
和非 undefined
1 2 3 let mayNullOrUndefinedOrString : null | undefined | string ;mayNullOrUndefinedOrString!.toString (); mayNullOrUndefinedOrString.toString ();
确认赋值断言 允许在实例属性和变量声明后面放置一个 !
号,从而告诉 TypeScript 该属性会被明确地赋值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let x : number ;initialize ();console .log (2 * x); function initialize ( ) { x = 10 ; } let x!: number ;initialize ();console .log (2 * x); function initialize ( ) { x = 10 ; }
双重断言 普通的断言是这样的
联合类型可以被断言为其中一个类型
父类可以被断言为子类
那,如果两个完全不兼容的断言,就要使用双重断言了,因为
任何类型都可以被断言为 any
any 可以被断言为任何类型
1 2 3 4 5 let bool : boolean = <boolean >n;let bool : boolean = n as any as boolean ;
类型扩宽
所有通过 let
或 var
定义的变量、函数的形参、对象的非只读属性,如果满足指定了初始值且未显式添加类型注解的条件,那么它们推断出来的类型就是指定的初始值字面量类型拓宽后的类型,这就是字面量类型拓宽(Type Widening)。
1 2 3 4 5 6 7 8 9 10 11 12 let str = 'this is string' ; let strFun = (str = 'this is string' ) => str; let x = null ; let y = undefined ; const specifiedStr = 'this is string' ; let str2 = specifiedStr; let strFun2 = (str = specifiedStr ) => str;
类型缩小
在TypeScript
中,我们可以通过某些操作将变量的类型由一个较为宽泛的集合缩小到相对较小、较明确的集合,这就是 “Type Narrowing”。
1 2 3 4 5 6 7 function func (anything: string | number ) { if (typeof anything === 'string' ) { return anything; } else { return anything; } }
你可以使用下面几个方式进行类型缩小
类型守卫:typeof
类型判断:===
控制流语句:if
,三目运算符,switch
分支
类型别名
类型别名用来给一个类型起个新名字
注意:类型别名,诚如其名,即我们仅仅是给类型取了一个新的名字,并不是创建了一个新的类型。
1 2 3 4 5 type Args = Array <string > | Array <number >;function log (...args: Args ) { console .log (...args); }
接口
在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。
什么是接口 在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。
TypeScript 中的接口是一个非常灵活的概念,除了可用于++「 对类的一部分行为进行抽象」++以外,也常用于对++「对象的形状(Shape)」++进行描述。
简单的示例 1 2 3 4 5 6 7 8 9 10 11 12 13 interface Person { name : string ; age : number ; } let tom : Person = { name : 'Tom' , age : 25 , gender : 'male' };
可选 & 只读属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 interface Person { readonly name : string ; readonly age : number ; readonly gender ?: boolean ; } let tom : Person = { name : 'Sakura' , age : 16 , }; tom.name = "Snow"
任意属性
有时候我们希望一个接口中除了包含必选和可选属性之外,还允许有其他的任意属性,这时我们可以使用 索引签名 的形式来满足上述要求。
1 2 3 4 5 6 7 8 9 10 11 12 13 interface Person { readonly name : string ; readonly age : number ; readonly gender ?: boolean ; [propName : string ]: any ; } let tom : Person = { name : 'Sakura' , age : 16 , data : {} };
需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
也就是下面的代码是不允许的
1 2 3 4 5 6 7 8 interface Person { readonly name : string ; readonly age : number ; readonly gender ?: boolean ; [propName : string ]: string ; }
接口和类型别名的区别 实际上,在大多数的情况下使用接口类型和类型别名的效果等价,但是在某些特定的场景下这两者还是存在很大区别。
TypeScript 的核心原则之一是对值所具有的结构进行类型检查。 而接口的作用就是为这些类型命名和为你的代码或第三方代码定义数据模型。
type(类型别名)会给一个类型起个新名字。 type 有时和 interface 很像,但是可以作用于原始值(基本类型),联合类型,元组以及其它任何你需要手写的类型。起别名不会新建一个类型 - 它创建了一个新 名字来引用那个类型。给基本类型起别名通常没什么用,尽管可以做为文档的一种形式使用。
语法不同 两者都可以用来描述对象或函数的类型,但是语法不同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 interface Point { x : number ; y : number ; } interface SetPoint { (x : number , y : number ): void ; } type Point = { x : number ; y : number ; }; type SetPoint = (x: number , y: number ) => void ;
接口可以自动合并 与类型别名不同,接口可以定义多次,会被自动合并为单个接口。
1 2 3 interface Point { x : number ; }interface Point { y : number ; }const point : Point = { x : 1 , y : 2 };
利用这点可以对系统数据结构进行扩展
1 2 3 interface Array <T> { remove (index : number ) : Array <T> }
接口可以在声明函数类型时同时声明一些属性 1 2 3 4 5 6 7 8 9 10 11 12 13 type Type = (num : number ) => number ;interface IType { (num : number ) : void ; _cache : Array <number > } const foo : IType = function ( ) { } foo._cache = []
我也很少用,也就在刷leetcode
时写斐波那契数列时用过
type可以声明一些interface无法表示的类型,比如union和tuple 1 2 3 4 5 type PartialPoint = PartialPointX | PartialPointY ;type Data = [number , string ];
扩展 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 PointX { x : number } interface Point extends PointX { y : number } type PointX = { x : number } type Point = PointX & { y : number } type PointX = { x : number } interface Point extends PointX { y : number } interface PointX { x : number } type Point = PointX & { y : number }
类
传统的面向对象语言都是基于类的,而JavaScript是基于原型的。在ES6中拥有了class关键字,虽然它的本质依旧是构造函数,但是能够让开发者更舒服的使用class了。 TypeScript 作为 JavaScript 的超集,自然也是支持 class 全部特性的,并且还可以对类的属性、方法等进行静态类型检测。
基本概念 基本使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 interface PointInterface { x : number ; y : number ; } class Point implements PointInterface { x : number ; y : number ; constructor (x: number , y: number ) { this .x = x; this .y = y; } getPosition ( ) { return `(${this .x} , ${this .y} )` ; } } const point = new Point (1 , 2 );point.getPosition ()
继承 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Animal { name : string ; constructor (name: string ) { this .name = name; } eat ( ) { console .log ("eat" ); } } class Cat extends Animal { age : number ; constructor (name: string , age: number ) { super (name); this .age = age; } play ( ) { console .log ("play" ); } }
如上,Cat
继承Animal
,那Animal
被称为父类 (超类),Cat
被称为子类 (派生类)。此时Cat
的实例继承了基类Animal
的属性和方法。
需要注意,派生类如果包含一个构造函数constructor
,则必须在构造函数中调用 super()
方法,这是 TypeScript
强制执行的一条重要规则。否则就会报错:Constructors for derived classes must contain a 'super' call.
那这个 super()
有什么作用呢?其实这里的 super
函数会调用基类的构造函数,用于数据初始化。
类的修饰符 在 ES6 标准类的定义中,默认情况下,定义在实例的属性和方法会在创建实例后添加到实例上;
而如果是定义在类里没有定义在this
上的方法,实例可以继承这个方法;而如果使用 static
修饰符定义的属性和方法,是静态属性和静态方法,实例是没法访问和继承到的。
传统面向对象语言通常都有访问修饰符,可以通过修饰符来控制可访问性。TypeScript
中有三类访问修饰符:
public
:修饰的是在任何地方可见、公有的属性或方法;
private
:修饰的是仅在同一类中可见、私有的属性或方法;
protected
:修饰的是仅在类自身及子类中可见、受保护的属性或方法。
此外,在类中可以使用readonly
关键字将属性设置为只读
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 36 37 38 39 40 41 42 class Stack <T> { private readonly arr : Array <T> = []; public static readonly Name = "Stack" ; public static isStack (o : any ): o is Stack <any > { return o instanceof Stack ; } get length () { return this .arr .length ; } public push (...args: Array <T> ) { this .arr .push (...args); } public pop (): T { let v = this .arr [this .length - 1 ]; this .arr .length --; return v; } public isEmpty ( ) { return !this .length ; } protected forEach (fun: Function ) { let cache = this .arr .splice (0 , this .arr .length ); cache.forEach ((value ) => { fun (value); }) } } let stack = new Stack <number >();stack.push (1 ); stack.push (2 ); console .log (stack.pop ());console .log (stack.pop ());console .log (stack.length );console .log (Stack .isStack (stack));console .log (Stack .Name );
抽象类 抽象类是一种不能被实例化的类,它的目的就是用来继承的,抽象类里面可以有抽象的成员,就是自己不实现,等着子类去实现。
抽象类和抽象成员,都是用abstract
修饰
抽象类中还是可以有具体实现的,这样子类如果不实现,可以继承抽象类中的实现。
1 2 3 4 5 6 abstract class Animal { abstract makeSound (): void ; move (): void { console .log ('roaming the earch...' ); } }
泛型 泛型的定义 软件工程中,我们不仅要创建一致的定义良好的 API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
在像 C# 和 Java 这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
设计泛型的关键目的是在成员之间提供有意义的约束,这些成员可以是:类的实例成员、类的方法、函数参数和函数返回值。
举个例子
1 2 3 4 5 6 function identity <T, U>(value : T, message : U) : T { console .log (message); return value; } console .log (identity<Number , string >(68 , "Semlinker" ));
其中 T
代表 Type ,在定义泛型时通常用作第一个类型变量名称。但实际上 T
可以用任何有效名称代替。除了 T
之外,以下是常见泛型变量代表的意思:
K(Key):表示对象中的键类型;
V(Value):表示对象中的值类型;
E(Element):表示元素类型。
泛型的使用 在接口中使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 interface Identities <V, M> { value : V, message : M } function identity<T, U> (value : T, message : U): Identities <T, U> { console .log (value + ": " + typeof (value)); console .log (message + ": " + typeof (message)); let res : Identities <T, U> = { value, message }; return res; } console .log (identity (16 , "Sakura" ));console .log (identity<number , string >(16 , "Sakura" ))
在类中使用 直接拿Array
举例
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 interface Array <T> { length : number ; pop (): T | undefined ; push (...items : T[]): number ; map<U>(callbackfn : (value: T, index: number , array: T[] ) => U, thisArg?: any ): U[]; } interface ArrayConstructor { new (arrayLength?: number ): any []; <T>(arrayLength : number ): T[]; isArray (arg : any ): arg is any []; readonly prototype : any []; } declare var Array : ArrayConstructor ;
用法
1 2 3 let arr : Array <number > = new Array <number >();arr.push (1 );
泛型约束 泛型的一个应用场景是确保属性存在,有时候,我们希望类型变量对应的类型上存在某些属性。这时,除非我们显式地将特定属性定义为类型变量,否则编译器不会知道它们的存在。
1 2 3 4 5 6 7 8 9 interface Length { length : number ; } function identity<T extends Length >(arg : T): T { console .log (arg.length ); return arg; }
泛型约束的另一个常见的使用场景就是检查对象上的键是否存在。不过在看具体示例之前,我们得来了解一下 keyof
操作符,**keyof
操作符是在 TypeScript 2.1 版本引入的,该操作符可以用于获取某种类型的所有键,其返回类型是联合类型。**
1 2 3 4 5 6 7 8 9 interface Person { name : string ; age : number ; location : string ; } type K1 = keyof Person ; type K2 = keyof Person []; type K3 = keyof { [x : string ]: Person };
通过 keyof
操作符,我们就可以获取指定类型的所有键,之后我们就可以结合前面介绍的 extends
约束,即限制输入的属性名包含在 keyof
返回的联合类型中。
1 2 3 function getProperty<T, K extends keyof T>(obj : T, key : K): T[K] { return obj[key]; }
泛型参数默认类型 一看就懂,和函数一样的
1 2 3 4 5 6 interface User <T = string > { name : T; } const strA : User = {name : "Sakura" };const numB : User <number > = {name : 101 };
高级语法 / 用法 typeof typeof
的主要用途是在类型上下文中获取变量或者属性的类型
1 2 3 4 5 6 interface Person { name : string ; age : number ; } const sakura : Person = { name : "Sakura" , age : 16 };type SakuraType = typeof sakura;
keyof keyof
操作符是在 TypeScript 2.1 版本引入的,该操作符可以用于获取某种类型的所有键,其返回类型是联合类型。
为了同时支持两种索引类型,就得要求数字索引的返回值必须是字符串索引返回值的子类。其中的原因就是当使用数值索引时,JavaScript 在执行索引操作时,会先把数值索引先转换为字符串索引 。所以 keyof { [x: string]: Person }
的结果会返回 string | number
。
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 interface Person { name : string ; age : number ; } type K1 = keyof Person ; type K2 = keyof Person []; type K3 = keyof { [x : string ]: Person }; interface StringArray { [index : string ]: string ; } interface StringArray1 { [index : number ]: string ; } let K1 : keyof boolean ; let K2 : keyof number ; let K3 : keyof symbol ;
in in
用来遍历联合类型,主要用于数组和对象的构建
1 2 3 4 5 type Keys = "a" | "b" | "c" type Obj = { [p in Keys ]: any }
infer infer
用于提取那个位置的类型值
1 2 3 4 5 6 7 8 9 10 11 function getSchool (name: string , age: number , address: string ) { return {name, age, address} } type ReturnType <T extends (...args : any []) => any > = T extends (...args : any []) => infer R ? R : any type MyReturnType = ReturnType <typeof getSchool>; type Parameters <T extends (...args : any []) => any > = T extends (...args : infer P) => any ? P : never type MyParamaters = Parameters <typeof getSchool>
以上代码中 infer R
就是声明一个变量来承载传入函数签名的返回值类型,简单说就是用它取到函数返回值的类型方便之后使用。
索引类型 1 2 3 4 5 6 7 8 9 class Person { name : string ; age : number ; } type MyType = Person ['name' ];
用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 interface Person { name : string ; age : number ; } const person : Person = { name : 'sakura' , age : 16 } function getValues<T, K extends keyof T>(person : T, keys : K[]): T[K][] { return keys.map (key => person[key]); } getValues (person, ['name' ]) getValues (person, ['gender' ])
映射类型 根据旧的类型创建出新的类型, 我们称之为映射类型
常见的有
Partial
Partial<T>
将类型的属性变成可选
1 2 3 4 5 6 type Partial <T> = { [P in keyof T]?: T[P]; }; type PartialPerson = Partial <Person >;
Required
Required将类型的属性变成必选
1 2 3 type Required <T> = { [P in keyof T]-?: T[P] };
Readonly
Readonly<T>
的作用是将某个类型所有属性变为只读属性,也就意味着这些属性不能被重新赋值。
1 2 3 type Readonly <T> = { readonly [P in keyof T]: T[P]; };
Pick
Pick 从某个类型中挑出一些属性出来
1 2 3 type Pick <T, K extends keyof T> = { [P in K]: T[P]; };
Record
Record
的作用是将 K
中所有的属性的值转化为 T
类型。
1 2 3 type Record <K extends keyof any , T> = { [P in K]: T; };
ReturnType
用来得到一个函数的返回值类型
理解为:如果 T
继承了 extends (...args: any[]) => any
类型,则返回类型 R
,否则返回 any
。其中 R
是什么呢?R
被定义在 extends (...args: any[]) => infer R
中,即 R 是从传入参数类型中推导出来的。
1 2 type ReturnType <T extends (...args : any []) => any > = T extends (...args : any []) => infer R ? R : any ;
Parameters
Parameters<T>
的作用是用于获得函数的参数类型组成的元组类型。
1 2 type Parameters <T extends (...args : any ) => any > = T extends (...args : infer P) => any ? P : never ;
Exclude
Exclude
的作用是将某个类型中属于另一个的类型移除掉。
1 2 type Exclude <T, U> = T extends U ? never : T;
如果 T
能赋值给 U
类型的话,那么就会返回 never
类型,否则返回 T
类型。最终实现的效果就是将 T
中某些属于 U
的类型移除掉。
用法
1 2 3 type T0 = Exclude <"a" | "b" | "c" , "a" >; type T1 = Exclude <"a" | "b" | "c" , "a" | "b" >; type T2 = Exclude <string | number | (() => void ), Function >;
Extract<T, U>
的作用是从 T
中提取出 U
1 type Extract <T, U> = T extends U ? T : never ;
用法
1 2 type T0 = Extract <"a" | "b" | "c" , "a" | "f" >; type T1 = Extract <string | number | (() => void ), Function >;
Omit
Omit<T, K extends keyof any>
的作用是使用 T
类型中除了 K
类型的所有属性,来构造一个新的类型。
1 type Omit <T, K extends keyof any > = Pick <T, Exclude <keyof T, K>>;
用法
1 2 3 4 5 6 7 8 9 10 11 12 interface Todo { title : string ; description : string ; completed : boolean ; } type TodoPreview = Omit <Todo , "description" >;const todo : TodoPreview = { title : "Clean room" , completed : false , };
NonNullable
NonNullable<T>
的作用是用来过滤类型中的 null
及 undefined
类型。
1 type NonNullable <T> = T extends null | undefined ? never : T;
用法
1 2 type T0 = NonNullable <string | number | undefined >; type T1 = NonNullable <string [] | null | undefined >;
其他 tsconfig.json的配置 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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 { "compilerOptions" : { "target" : "es5" , "module" : "commonjs" , "lib" : [], "allowJs" : true , "checkJs" : true , "jsx" : "preserve" , "declaration" : true , "sourceMap" : true , "outFile" : "./" , "outDir" : "./" , "rootDir" : "./" , "removeComments" : true , "noEmit" : true , "importHelpers" : true , "isolatedModules" : true , "strict" : true , "noImplicitAny" : true , "strictNullChecks" : true , "noImplicitThis" : true , "alwaysStrict" : true , "noUnusedLocals" : true , "noUnusedParameters" : true , "noImplicitReturns" : true , "noFallthroughCasesInSwitch" : true , "moduleResolution" : "node" , "baseUrl" : "./" , "paths" : {}, "rootDirs" : [], "typeRoots" : [], "types" : [], "allowSyntheticDefaultImports" : true , "sourceRoot" : "./" , "mapRoot" : "./" , "inlineSourceMap" : true , "inlineSources" : true , "experimentalDecorators" : true , "emitDecoratorMetadata" : true } }
参考 https://juejin.cn/post/7050290769562697736
https://juejin.cn/post/6844904184894980104
https://juejin.cn/post/7000182870404759589
https://blog.csdn.net/lhjuejiang/article/details/119038312
https://juejin.cn/post/6999441997236797470
https://juejin.cn/post/6844904146877808653
https://juejin.cn/post/6844904146877808653