TypeScript接口的10种使用场景——可能只有20%的web开发人员完全掌握它们
TypeScript中的接口是一个非常灵活的概念。除了抽象类的部分行为外,它还经常用于描述“对象的形状”。
必需的属性
在定义接口时,需要使用 interface
关键字:
interface User {
name: string;
sex: string;
}
const user: User = {
name: "Bytefer",
sex: "male",
};
在上面的代码中,我们定义了一个 User
接口。然后定义一个 user
变量,并将其类型设置为 User
类型。但是,如果给 user
变量赋值时如果缺少相关的属性,TypeScript编译器会提示相关的错误。例如,在下面的代码中,我们在赋值时缺少 sex
属性:
那么如何解决上述错误呢? 解决方案之一是在定义接口时使用 ?
声明一些属性是可选的。
可选属性
interface User {
name: string;
sex?: string;
}
let user: User = { // OK
name: "Bytefer",
};
user = { // Ok name: "Bytefer",
sex: "male",
};
这样在赋值是不管有没有 sex
属性就都不会报错了。
那么是否可以添加未声明的属性呢?
从上图可以看出,当使用对象字面量赋值时,包括未声明的 age
属性,也会出现错误。解决这个问题最简单的方法是在 User
类型中添加 age
属性:
interface User {
name: string;
sex?: string;
age: number;
}
虽然这种解决方案可以解决问题,但是如果我们想要添加其他任意属性,这种方式就不是很好了。为了满足上述要求,我们可以使用索引签名。
索引签名
索引签名的语法如下:
键的类型只能是字符串、数字、符号或模板文字类型,而值的类型可以是任何类型。
现在我们理解了索引签名的语法,让我们来更新 User
类型:
interface User {
name: string;
sex?: string;
[propName: string]: any; // Index Signatures
}
在更新了 User
类型,并添加了新的 age
和 email
属性之后,TypeScript编译器就不会提示错误了。
let user: User = {
name: "Bytefer",
sex: "male",
age: 30,
email: "bytefer@gmail.com"
};
只读的属性
在web系统中,我们需要区分不同的用户,一般来说,我们会使用 id
属性来识别不同的用户。此属性由Web系统自动生成,用户不能修改。对于上面的场景,我们可以使用 readonly
修饰符来定义只读属性。
除了属性,对象还可以包含方法。当使用接口定义对象类型时,还可以声明同时存在于对象上的方法:
interface User {
id: string;
name: string;
say(words: string): void;
}
let user: User = { id: "6666",
name: "Bytefer",
say(words: string) {
console.log(words);
},
};
调用签名
描述函数最简单的方法是使用函数类型表达式。这些类型在语法上类似于箭头函数:
const log: (msg: string) => void = (msg: string) => {
console.log(msg);
};
log("Bytefer");
语法 (msg: string) => void
表示“具有一个名为 msg
的字符串类型参数的函数,该函数没有返回值”。当然,我们可以使用类型别名来命名函数类型:
type LogFn = (msg: string) => void;
const log: LogFn = (msg: string) => {
console.log(msg);
};
如果我们想用属性描述一些可调用的东西,函数本身也是一个对象。那么函数类型表达式就不能满足这个要求。对于这种情况,我们可以在定义对象类型时使用调用签名:
需要注意的是,在声明调用签名时,还支持重载:
interface Logger {
type: string;
(msg: string): void;
(msg: string, timestamp: number): void
(msg: string, timestamp: number, module: string): void
}
构造签名
除了直接调用函数外,还可以使用 new
操作符来调用函数,这些函数通常称为构造函数。我们可以通过在调用签名前添加 new
关键字来编写构造签名:
interface PointConstructor {
new (x: number, y: number): { x: number; y: number };
}
function createPoint(ctor: PointConstructor, x: number = 0, y: number = 0) {
return new ctor(x, y);
}
class Point { constructor(public x: number, public y: number) {}
}
const zero = createPoint(Point);console.log(zero);
混合类型
因此,在定义接口时,我们可以同时使用调用签名和构造签名吗? 答案是肯定的,我们常用的 Date
对象,它的类型是 DateConstructor
,其中调用签名和构造签名都被使用:
declare var Date: DateConstructor;
在上面的代码中,除了调用签名和构造签名之外,还定义了 Date
构造函数的属性和方法。
通用接口
泛型类型也可以与接口一起使用。下面是一个通用接口。
interface KeyPair<T, U> {
key: T;
value: U;
}
let kv1: KeyPair<number, string> = { key: 1, value: "Bytefer" };
扩展接口
接口可以扩展一个或多个接口。这使得编写接口变得灵活和可重用。
interface Point1D {
x: number;
}
interface Point2D extends Point1D { y: number;
}
interface Point3D extends Point2D { z: number;
}
const point1D = { x: 0 };const point2D = { x: 0, y: 0 };
const point3D = { x: 0, y: 0, z: 0 };
除了扩展单个接口,TypeScript还允许我们扩展多个接口:
interface CanSay {
say(words: string) :void
}
interface CanWalk { walk(): void;
}
interface Human extends CanSay, CanWalk { name: string;
}
扩展类
在声明接口时,我们可以扩展一个或多个接口。实际上,我们也可以扩展已声明的类:
class Point1D {
public x!: number;
}
interface Point2D extends Point1D { y: number;
}
const point2D: Point2D = { x: 0, y: 0 }
对于一个类,在声明该类时,可以同时实现多个接口:
interface CanSay {
say(words: string) :void
}
interface CanWalk { walk(): void;
}
class Person implements CanSay, CanWalk { constructor(public name: string) {}
public say(words: string) :void {
console.log(`${this.name} says:${words}`);
}
public walk(): void {
console.log(`${this.name} walk with feet`);
}
}
对于TypeScript开发者来说,接口和类型有很多相似之处,当然也有一些不同之处,需要根据场景灵活运用。
欢迎关注公众号:文本魔术,了解更多