一、介绍
typescript的核心设计原则之一是对值所具有的结构进行类型检查,接口的作用就是为类型
命名和为代码结构定义契约,一个简单例子如:
function showName(person: { name: string; age: number }) {
console.log(person.name)
}
这里限定:传给showName()
函数的参数person
,必须具有{ name: string, age: number }
的结构,所以使用如下:
let p = {
name: 'RuphiLau',
age: 21
}
showName(p)
如果变量p
没有完整具有name
和age
两个相应类型的属性,就会报错,我们可以将限定p
类型的结构起个名称为Person
,定义接口如下:
interface Person {
name: string
age: number
}
从而改写如下:
function showName(person: Person) {
// ...
}
二、可选属性
可选属性允许我们可以只实现接口里的部分定义,用法为在名称后面加上?
,如:
interface Person {
name: string
age: number
isCoder?: boolean
}
此时,以下两种写法都是合法的:
let p: Person = {
name: 'RuphiLau',
age: 21
}
let p2: Person = {
name: 'RuphiLau',
age: 21,
isCoder: true
}
三、只读属性
如果一个属性只能在刚刚创建的时候修改值,那么可以在属性名前用readonly
来指定只读属性,如:
interface Point {
readonly x: number
readonly y: number
}
使用如下:
let p: Point = { x: 10, y: 20 }
p.x = 100 // 报错
注意:typescript中有ReadonlyArray<T>
类型,它与Array<T>
类型类似,只不过创建过后,就不允许数组再修改了,如:
let arr: number[] = [1, 2, 3, 4]
let roArr: ReadonlyArray<number> = arr
roArr.push(5) // 报错
roArr.length = 5 // 报错
即使再把ReadonlyArray
类型的数组赋给普通类型的数组,也是不行的,但是可以使用类型断言,如:
arr = roArr // 报错
roArr as number[] // 可以
readonly vs const?
一般情况下,readonly
用于属性,而const
则用于变量
四、额外的属性检查
当一个对象字面量里声明了接口中不存在的属性时,会报错不存在错误,如:
interface Demo {
a: number
b: number
}
let d: Demo = {
a: 1,
b: 2,
c: 3
}
// 报错: Object literal may only specify known properties, and 'c' does not exist in type 'Demo'.
这是因为TS会对对象字面量
会进行额外的属性检查,解决这个问题,可以使用类型断言,如:
let d: Demo = {a:1, b:2, c:3} as Demo
更好的方法,则是使用字符串索引签名
,如:
interface Demo {
a: number
b: number
[c: string]: any
}
五、类型签名
接口能够描述JavaScript中对象拥有的各式各样的外形,除了描述带有属性的普通对象外,还可以描述:
1、描述函数
可以给接口定义一个调用签名,如:
interface MyFn {
(a: number, b: number): boolean
}
let fn: MyFn
fn = function(a: number, b: number): boolean {
return a > b
}
需要注意的是:函数的参数名并不需要和接口定义里的参数名
相匹配,只要类型是兼容的就可以了
2、描述索引
可以描述索引,如:
interface StringArray {
[index: string]: string
}
let x: StringArray
x.name = 'RuphiLau' // 可以
x.age = 21 // 报错
索引签名支持两种类型:number
和string
,但是由于number
实际上会被转化为string
类型(根据对象key的性质),所以需要遵守:number
索引的返回值类型是string
索引的返回值类型的子类型,所以以下的做法是错误的:
class A {
propA: string
}
class B extends A {
propB: string
}
interface Demo {
[index: number]: A
[index: string]: B
}
// 报错:Numeric index type 'A' is not assignable to string index type 'B'.
// 这是因为,如obj[100]和obj['100']是视为一样的,故无法辨别该返回A还是返回B
如果interface
里还声明了一个和索引签名索引返回值类型
不匹配的属性,会报错,如下:
interface Demo {
[index: string]: string
name: string // 没问题,因为返回值类型是string
age: number // 报错,因为返回值类型是number,不符合string类型
}
最后,我们还可以声明一个readonly
的索引签名,如:
interface ReadonlyStringArray {
readonly [index: number]: string
}
let x: ReadonlyStringArray = ['RuphiLau']
x[0] = 'RuphiLau'
// 报错:Index signature in type 'ReadonlyStringArray' only permits reading.
六、类类型
typescript里也允许像Java、C#那样,让一个class去实现一个interface,如:
interface ISome {
prop: string // 描述一个属性
method(paramA: string, paramB: number) // 描述一个方法
}
class A implements ISome {
prop: 'propValue'
method(a: string, b: number) {
// ...
}
constructor(paramA: number){}
}
但是需要注意的是,接口描述的是类的公共部分,而不是公共和私有两部分,所以不会检查类是否具有某些私有成员。
1、静态部分
与实例部分
首先看一个示例:用构造器签名
定义一个接口,并试图实现这个接口:
interface Person {
new(name: string)
}
class People implements Person {
constructor(name: string) {
// ...
}
}
// 报错:no match for the signature 'new (name: string): any'.
这是因为:当类实现一个接口时,只对实例部分进行类型检查,而constructor
存在于静态部分,所以不在检查的范围内
所以做法如下:
// 针对类构造函数的接口
interface CPerson {
new(name: string)
}
// 针对类的接口
interface IPerson {
name: string
age: number
}
function create(c: CPerson, name: string): IPerson {
return new c(name)
}
class People implements IPerson {
name: string
age: number
}
let p = create(People, 'RuphiLau') // 可以
2、继承接口
和类一样,接口也可以相互继承,如:
interface Shape {
color: string
}
interface Square extends Shape {
sideLength: number
}
const square = <Square>{}
square.color = 'blue'
square.sideLength = 10
同时,一个接口也可以继承多个接口,创建出多个接口的合成接口,如:
interface Shape {
color: string
}
interface PenStroke {
penWidth: number
}
interface Square extends Shape, PenStroke {
sideLength
}
const square = <Square>{}
square.color = 'blue'
square.sideLength = 10
square.penWidth = 5.0
3、混合类型
允许让一个对象同时作为函数和对象使用,并带有额外的属性,如:
interface MixedDemo {
(str: string): void
defaultStr: string
}
function foo(): MixedDemo {
let x = <MixedDemo>function(str: string){
console.log(str)
}
x.defaultStr = 'Hello, world'
return x
}
let c = foo()
c('This is a function') // 输出:'This is a function'
console.log(c.defaultStr) // 输出:'Hello, world'
4、接口继承类
接口可以继承自一个类,从而像声明了所有类中存在的成员,并且private
和protected
成员也会被继承,这意味着:只有类自己或子类能够实现该接口,例子如:
class A {
protected propA: string
}
interface I extends A {
method(): void
}
// 下面这种做法会报错
class C implements A {
// 因为propA是类A的保护成员,只有自身和子类可实现
// 但类C不是A的子类
protected propA: string
method() {}
}
// 下面这种做法则是允许的
class C extends A implements A {
protected propA: string
method() {}
}