1.什么是接口
在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。
TypeScript 中的接口是一个非常灵活的概念,除了可用于[对类的一部分行为进行抽象]以外,也常用于对「对象的形状(Shape)」进行描述。
2.基本使用
当一个对象类型被多次使用时,一般会使用接口(interface
)来描述对象的类型,达到复用的目的
- 解释:
- 使用
interface
关键字来声明接口 - 接口名称(比如,此处的 Person),可以是任意合法的名称
- 声明接口后,直接使用接口名称作为类型
- 使用
interface IPerson {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25
};
上面的例子中,我们定义了一个接口 IPerson
,接着定义了一个变量 tom
,它的类型是 IPerson
。这样,我们就约束了 tom
的形状必须和接口 IPerson
一致。
接口一般首字母大写。
定义的变量比接口少了一些属性是不允许的:
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom'
};
// index.ts(6,5): error TS2322: Type '{ name: string; }' is not assignable to type 'Person'.
// Property 'age' is missing in type '{ name: string; }'.
多一些属性也是不允许的:
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
// index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.
可见,赋值的时候,变量的形状必须和接口的形状保持一致。
3.可选 | 只读属性
interface Person {
readonly name: string;
age?: number;
}
只读属性用于限制只能在对象刚刚创建的时候修改其值。此外 TypeScript 还提供了 ReadonlyArray<T>
类型,它与 Array<T>
相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改。
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
4.任意属性
有时候我们希望一个接口中除了包含必选和可选属性之外,还允许有其他的任意属性,这时我们可以使用 索引签名 的形式来满足上述要求。
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
};
需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
interface Person {
name: string;
age?: number;
[propName: string]: string;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Index signatures are incompatible.
// Type 'string | number' is not assignable to type 'string'.
// Type 'number' is not assignable to type 'string'.
上例中,任意属性的值允许是 string
,但是可选属性 age
的值却是 number
,number
不是 string
的子属性,所以报错了。
另外,在报错信息中可以看出,此时 { name: 'Tom', age: 25, gender: 'male' }
的类型被推断成了 { [x: string]: string | number; name: string; age: number; gender: string; }
,这是联合类型和接口的结合。
一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:
interface Person {
name: string;
age?: number; // 这里真实的类型应该为:number | undefined
[propName: string]: string | number | undefined;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
5.绕开额外属性检查的方式
5.1 类型断言
类型断言的意义就等同于你在告诉程序,你很清楚自己在做什么,此时程序自然就不会再进行额外的属性检查了。
interface Props {
name: string;
age: number;
money?: number;
}
let p: Props = {
name: "兔神",
age: 25,
money: -100000,
girl: false
} as Props; // OK
5.2 索引签名
使用场景:当无法确定对象中有那些属性(或对象中可以出现任意多的属性) 使用[key:string] 来约束该接口中允许出现的属性名称。表示只要是string类型的属性名称,都可以出现在对象中 key只是一个占位符,可以换成任意合法的变量名称。
interface AnyObject {
[key:string]:number
}
let obj1:AnyObject ={
'1':11,
// 's':'ss'
}
// ||
// \/
interface AnyObject1<T> {
[key:string]:T
}
let obj2:AnyObject1<number> ={
'1':11,
// 's':'ss'
}
数组接口也是使用了索引签名类型
interface MyArray<T> {
[n:number]:T
}
let mayy:MyArray<number> = [1,2]
MyArray模拟了原生的数组接口 并使用[n:number]来作为索引签名类型 该签名表示:只要是number类型的键/索引都可以出现在数组中,或者说数组中可以有任意多个元素 同时也符合数组索引十number类型的这一前提。