在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implements),TypeScript 中的接口除了可用于对类的一部分行为进行抽象以外,也常用于对对象的形状(Shape)进行描述。
接口初探
类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以。
interface IPerson{
name: string;
age: number
}
function say(person: IPerson): void {
console.log(`my name is ${person.name}, and age is ${person.age}`)
}
let me = {
name: 'funlee',
age: 18
}
say(me); // my name is funlee, and age is 18
复制代码
可选属性
定义可选属性的接口,只需在可选属性名字的定义后面加一个 ? 符号
interface IPerson {
name: string;
age: number;
love?: string
}
function say(person: IPerson): void {
if(person.hasOwnProperty('love')) {
console.log(`my name is ${person.name}, my age is ${person.age}, and I love ${person.love}`);
} else {
console.log(`my name is ${person.name}, and age is ${person.age}`);
}
}
let me = {
name: 'funlee',
age: 18,
love: 'TS'
}
let you = {
name: 'Jack',
age: 18
}
say(me); // my name is funlee, my age is 18, and I love TS
say(you) // my name is Jack, and age is 18
复制代码
只读属性
定义只读属性的接口,只需在只读属性名前加 readonly
interface IPerson {
readonly name: string;
age: number;
love?: string
}
let me: IPerson = {
name: 'funlee',
age: 18
}
me.name = 'new name'; // error!
复制代码
额外的属性检查
当一个对象字面量里声明了接口中不存在的属性时,会报不存在错误,即使该属性已设置为可选属性,因为该对象字面量会被特殊对待而且会经过额外属性检查,绕开额外属性检查的方法如下:
- 使用类型断言
- 添加一个字符串索引签名,前提是你能够确定这个对象可能具有某些做为特殊用途使用的额外属性
- 将该对象赋值给另一个变量
// 错误写法,会进行额外检查
interface IPerson {
name: string;
age?: number;
}
let me: IPerson = {
name: 'funlee',
love: 'TS'
}
// 方法一:类型断言
interface IPerson {
name: string;
age?: number;
}
let me = {
name: 'funlee',
love: 'TS'
} as IPerson
// 方法二:字符串索引签名
interface IPerson {
name: string;
age?: number;
[propName: string]: any;
}
let me: IPerson = {
name: 'funlee',
love: 'TS'
}
// 方法三:赋值给另一个变量
interface IPerson {
name: string;
age?: number;
}
let me = {
name: 'funlee',
love: 'TS'
}
let you: IPerson = me;
复制代码
函数类型
接口可以描述函数类型,它定义了函数的参数列表和返回值类型,参数列表里的每个参数都需要名字和类型,函数的参数名不需要与接口里定义的名字相匹配,只需要类型兼容就可以了。
let getArea: (width: number, height: number) => number = (w: number, h: number): number =>{
return w * h;
}
console.log(getArea(5, 6)) // 30
复制代码
可索引的类型
接口可以描述那些能够通过索引得到的类型,可索引类型具有一个索引签名,它描述了对象索引的类型,还有相应的索引值类型,索引签名支持两种类型:number 和 string,但是由于 number 实际上会被转化为 string 类型(根据对象 key 的性质),所以需要遵守:number 索引的返回值类型是 string 索引的返回值类型的子类型。
interface IPerson {
[index: string]: string;
}
let me: IPerson = {love: 'TS'}
me.name = 'funlee';
me.age = 18; // error
复制代码
如果 interface 里还声明了一个和索引签名索引返回值类型不匹配的属性,会报错
interface ITest {
[index: string]: string;
name: string;
age: 18; // 报错,因为返回值类型是number,不符合string类型
}
复制代码
还可以声明一个 readonly 的索引签名
interface IPerson {
readonly [index: string]: string;
}
let p: IPerson = {name: 'funlee'};
p.love = 'TS'; // error
复制代码
类类型
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){
// ...
}
}
复制代码
静态部分与实例部分
首先看一个示例:用构造器签名定义一个接口,并试图实现这个接口:
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, 'funlee') // 可以
复制代码
继承接口
和类一样,接口也可以相互继承,如:
interface Shape {
color: string;
}
interface Square extends Shape {
sideLength: number;
}
const square = {};
square.color = 'blue';
square.sideLength = 10;
复制代码
同时,一个接口也可以继承多个接口,创建出多个接口的合成接口,如:
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength
}
const square = {};
square.color = 'blue';
square.sideLength = 10;
square.penWidth = 5.0;
复制代码
混合类型
允许让一个对象同时作为函数和对象使用,并带有额外的属性,如:
interface MixedDemo {
(str: string): void;
defaultStr: string;
}
function foo(): MixedDemo {
let x = 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'
复制代码
接口继承类
接口可以继承自一个类,从而像声明了所有类中存在的成员,并且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() {}
}
复制代码