接口
- 接口既可以在面向对象编程中表示为行为的抽象,也可以用来描述对象的形状
- 我们用
interface
关键字来定义接口 在接口中可以用分号或者逗号分割每一项,也可以什么都不加 - 定义的变量比接口少了一些属性是不允许的,多一些属性也是不允许的。
- 赋值的时候,变量的形状必须和接口的形状保持一致
- 接口一般首字母大写。有的编程语言中会建议接口的名称加上
I
前缀。
//接口可以用来描述`对象的形状`
interface Speakable {
speak(): void;
readonly lng: string; //readonly表示只读属性 后续不可以更改
name?: string; //?表示可选属性
}
let speakman: Speakable = {
// speak() {}, //少属性会报错
name: "hello",
lng: "en",
age: 111, //多属性也会报错
};
- 接口可以把一些类中共有的属性和方法抽象出来,可以用来约束实现此接口的类
- 一个类可以实现多个接口,一个接口也可以被多个类实现
interface Speakable{
speak():void
}
interface Eatable {
eat ():void
}
//一个类可以实现多个接口
class Person implements Speakable,Eatable {
speak(){}
eat(){} 需要实现的接口包含eat方法 不实现会报错
}
定义任意属性
-
如果我们在定义接口的时候无法预先知道有哪些属性的时候,可以使用
[propName:string]:any
,propName 名字是任意的interface Person { id : number , name :string , [propName :string] :any } let p1 = { id: 1, name: "hello", age: 10, }; //这个接口表示 必须要有 id 和 name 这两个字段 然后还可以新加其余的未知字段
接口的继承
-
同样的使用
extends
关键字interface Speakable { speak(): void; } interface SpeakChinese extends Speakable { speakChinese(): void; } class Person implements SpeakChinese { speak() { console.log("Person"); } speakChinese() { console.log("speakChinese"); } }
函数类型接口
-
用接口定义函数类型
interface discount { (price :number) :number } let cost:discount =function(price:number):number { return price *0.8 } console.log(cost(20));
构造函数的类型接口
-
使用特殊的new()关键字来描述类的构造函数类型
class Animals { constructor (public name :string){} } interface WithNameClass { new(name:string):Animals } function createAnimals(clazz:WithNameClass,name :string){ return new clazz(name) } let a = createAnimals(Animals , 'hello') console.log(a.name); //hello
接口和类型别名的区别
在大多数的情况下使用接口类型和类型别名的效果等价,但是在某些特定的场景下这两者还是存在很大区别
-
基础数据类型 与接口不同,类型别名还可以用于其他类型,如基本类型(原始值)、联合类型、元组
type Name = string; // union type PartialPoint = PartialPointX | PartialPointY; // tuple type Data = [number, string]; // dom let div = document.createElement("div"); type B = typeof div;
-
重复定义
- 接口可以定义多次 会被自动合并为单个接口 类型别名不可以重复定义
interface Point { x: number; } interface Point { y: number; } const point: Point = { x: 1, y: 2 };
-
扩展 接口可以扩展类型别名,同理,类型别名也可以扩展接口。但是两者实现扩展的方式不同,接口的扩展就是继承,通过 extends 来实现。类型别名的扩展就是交叉类型,通过 & 来实现。
// 接口扩展接口 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; };
-
实现 这里有一个特殊情况 类无法实现定义了联合类型的类型别名
type PartialPoint = { x: number } | { y: number }; // A class can only implement an object type or // intersection of object types with statically known members. class SomePartialPoint implements PartialPoint { // Error x = 1; y = 2; }
泛型
-
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
-
通俗理解:泛型就是解决 类 接口 方法的重用性、以及对不特定数据类型的支持
改造前 function createArray(length: number, value: any): any[] { let result = []; for (let i = 0; i < length; i++) { result[i] = value; } return result; } createArray(3, "x"); // ['x', 'x', 'x'] 泛型改造后 function createArray<T>(length:number,value:T):Array<T>{ let result :T[]=[] for (let i = 0; i < length; i++) { result[i] = value; } return result } createArray<string>(3, "x"); // ['x', 'x', 'x']
- 我们可以使用<>的写法 然后再面传入一个变量 T 用来表示后续函数需要用到的类型 当我们真正去调用函数的时候再传入 T 的类型就可以解决很多预先无法确定类型相关的问题
多个类型参数
-
如果我们需要有多个未知的类型占位 那么我们可以定义任何的字母来表示不同的类型参数
function swap<T,U>(truple : [T,U]):[U,T]{ return [truple[1] , truple[0]] } console.log(swap([7, "seven"]));
泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法:
我们在泛型里面使用extends
关键字代表的是泛型约束 需要和类的继承区分开
interface Lengthwise{
length:number
}
function loggingIdentify<T extends Lengthwise>(arg:T):T{
console.log(arg.length);
return arg
}
泛型接口
- 定义接口的时候也可以指定泛型
interface Cart<T> {
list: T[];
}
let cart: Cart<{ name: string; price: number }> = {
list: [{ name: "hello", price: 10 }],
};
console.log(cart.list[0].name, cart.list[0].price);
- 我们定义了接口传入的类型 T 之后返回的对象数组里面 T 就是当时传入的参数类型
泛型类
class MyArray<T>{
private list : T[]=[];
add(value :T){
this.list.push(value)
}
getMax():T{
let result =this.list[0]
for(let i=0; i <this.list.length ; i++ ){
if(this.list[i]>result){
result =this.list[i]
}
}
return result
}
}
let arr=new MyArray()
arr.add(1)
arr.add(2)
arr.add(3)
let ret=arr.getMax()
console.log(ret);
泛型类型别名
type Cart<T> = { list :T[]} | T[]
let c1 : Cart<string> ={list : [ '1']}
let c2: Cart<number> ={list : [ 2]}
使用技巧
-
keyof
可以用来取得一个对象接口的所有 key 值 -
typeof 获取一个变量的类型
interface Person {}
type PersonKey = keyof Person;
-
使用 [] 操作符可以进行索引访问
interface Person { name: string; age: number; } type x = Person["name"]; // x is string
-
在定义的时候用 in 操作符去批量定义类型中的属性
interface Person { name: string; age: number; gender: "male" | "female"; } //批量把一个接口中的属性都变成可选的 type PartPerson = { [Key in keyof Person]?: Person[Key]; }; let p1: PartPerson = {};
-
在条件类型语句中,可以用 infer 声明一个类型变量并且对它进行使用。
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
- infer R 就是声明一个变量来承载传入函数签名的返回值类型,简单说就是用它取到函数返回值的类型方便之后使用。
-
工具类
-
Exclude<T,U> 从 T 可分配给的类型中排除 U
type Exclude<T, U> = T extends U ? never : T; type E = Exclude<string | number, string>; let e: E = 10;
-
Extract<T,U> 从 T 可分配给的类型中提取 U
-
NonNullable从 T 中排除
null
和undefined
-
ReturnType
infer
最早出现在此 PR 中,表示在extends
条件语句中待推断的类型变量 -
Parameters该工具类型主要是获取函数类型的参数类型
-
PartialPartial 可以将传入的属性由非可选变为可选
-
RequiredRequired 可以将传入的属性中的可选项变为必选项,这里用了 -? 修饰符来实现。
-
ReadonlyReadonly 通过为传入的属性每一项都加上 readonly 修饰符来实现。
-
Pick<T,K> Pick 能够帮助我们从传入的属性中摘取某些返回
-
Record<K,T> 构造一个类型,该类型具有一组属性 K,每个属性的类型为 T。可用于将一个类型的属性映射为另一个类型。Record 后面的泛型就是对象键和值的类型。
-
Omit<K,T> 基于已经声明的类型进行属性剔除获得新类型
-