接口
在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。
TypeScript 中的接口是一个非常灵活的概念,除了可用于[对类的一部分行为进行抽象]以外,也常用于对「对象的形状(Shape)」进行描述。
interface Person {
name: string
age: number
}
let tom: Person = {
name: 'Tom',
age: 25,
}
上面的例子中,我们定义了一个接口 Person,接着定义了一个变量 tom,它的类型是 Person。这样,我们就约束了 tom 的形状必须和接口 Person 一致。
接口一般首字母大写。
定义的变量比接口多或少了一些属性是不允许的:
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; }'.
let tom2: 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'.
可选 | 只读属性
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!
任意属性
有时候我们希望一个接口中除了包含必选和可选属性之外,还允许有其他的任意属性,这时我们可以使用 索引签名 的形式来满足上述要求。
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 的子属性,所以报错了。
一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:
interface Person {
name: string
age?: number // 这里真实的类型应该为:number | undefined
[propName: string]: string | number | undefined
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male',
}
接口与类型别名的区别
实际上,在大多数的情况下使用接口类型和类型别名的效果等价,但是在某些特定的场景下这两者还是存在很大区别。
type(类型别名)会给一个类型起个新名字。 type 有时和 interface 很像,但是可以作用于原始值(基本类型),联合类型,元组以及其它任何你需要手写的类型。起别名不会新建一个类型 - 它创建了一个新 名字来引用那个类型。给基本类型起别名通常没什么用,尽管可以做为文档的一种形式使用。
Objects / Functions
两者都可以用来描述对象或函数的类型,但是语法不同。
Interface
interface Point {
x: number
y: number
}
interface SetPoint {
(x: number, y: number): void
}
Type
type Point = {
x: number
y: number
}
type SetPoint = (x: number, y: number) => void
Other Types
与接口不同,类型别名还可以用于其他类型,如基本类型(原始值)、联合类型、元组。
// primitive
type Name = string
// object
type PartialPointX = { x: number }
type PartialPointY = { y: number }
// 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 }
扩展
接口扩展接口
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
}