本文献给:
已掌握 TypeScript 类型别名、联合类型、交叉类型等知识的开发者。本文将带你学习接口(interface)的核心用法,包括定义对象形状、可选属性、只读属性、多余属性检查,以及接口与类型别名的区别与选择。
你将学到:
- 接口的基本语法与使用场景
- 可选属性
?与只读属性readonly - 多余属性检查与绕过方法
- 接口与类型别名
type的对比 - 接口声明的合并特性
目录
- 一、接口的基本语法
-
- [1.1 接口作为类型注解](#1.1 接口作为类型注解)
- [二、可选属性(Optional Properties)](#二、可选属性(Optional Properties))
- [三、只读属性(Readonly Properties)](#三、只读属性(Readonly Properties))
-
- [3.1 只读数组](#3.1 只读数组)
- [四、多余属性检查(Excess Property Checks)](#四、多余属性检查(Excess Property Checks))
-
- [4.1 绕过多余属性检查的方法](#4.1 绕过多余属性检查的方法)
- 五、接口与类型别名的对比
-
- [5.1 声明合并](#5.1 声明合并)
- [5.2 扩展方式对比](#5.2 扩展方式对比)
- [5.3 选择建议](#5.3 选择建议)
- 六、接口的函数类型和索引签名(简介)
-
- [6.1 函数类型接口](#6.1 函数类型接口)
- [6.2 索引签名](#6.2 索引签名)
- 七、常见错误与注意事项
-
- [7.1 忘记使用 `=` 定义接口](#7.1 忘记使用
=定义接口) - [7.2 使用 `interface` 定义联合类型](#7.2 使用
interface定义联合类型) - [7.3 只读属性与 `const` 混淆](#7.3 只读属性与
const混淆) - [7.4 多余属性检查意外触发](#7.4 多余属性检查意外触发)
- [7.1 忘记使用 `=` 定义接口](#7.1 忘记使用
- 八、综合示例
- 九、小结
一、接口的基本语法
接口用于定义对象的结构形状(shape),描述对象应该有哪些属性以及属性的类型。
typescript
interface User {
id: number;
name: string;
email: string;
}
function register(user: User) {
console.log(`${user.name} registered`);
}
const alice: User = { id: 1, name: "Alice", email: "alice@example.com" };
register(alice);
接口可以定义函数类型、索引签名等,但最常见的是描述对象。
1.1 接口作为类型注解
接口可以用于变量、函数参数、函数返回值等任何需要类型的地方。
typescript
interface Point {
x: number;
y: number;
}
function getDistance(p1: Point, p2: Point): number {
return Math.hypot(p1.x - p2.x, p1.y - p2.y);
}
const origin: Point = { x: 0, y: 0 };
const target: Point = { x: 3, y: 4 };
console.log(getDistance(origin, target)); // 5
二、可选属性(Optional Properties)
使用 ? 标记属性为可选,表示该属性可以存在也可以不存在。
typescript
interface Person {
name: string;
age?: number; // 可选
email?: string; // 可选
}
const p1: Person = { name: "Bob" }; // OK
const p2: Person = { name: "Alice", age: 25 }; // OK
可选属性的类型实际上是 T | undefined。
typescript
function greet(person: Person) {
console.log(`Hello, ${person.name}`);
if (person.age !== undefined) {
console.log(`Age: ${person.age}`);
}
}
三、只读属性(Readonly Properties)
使用 readonly 关键字标记属性为只读,在对象创建后不可修改。
typescript
interface Config {
readonly apiUrl: string;
readonly timeout: number;
retries: number; // 可修改
}
const config: Config = {
apiUrl: "https://api.example.com",
timeout: 5000,
retries: 3
};
config.retries = 5; // OK
config.apiUrl = "xxx"; // ❌ 只读属性不可修改
readonly 只在编译时检查,不影响运行时。对象内容本身是否可以修改取决于属性是否是只读。
3.1 只读数组
TypeScript 提供了 ReadonlyArray<T> 类型,以及 readonly 修饰符用于数组和元组。
typescript
interface DataContainer {
readonly items: number[];
data: readonly string[]; // 只读数组
}
const container: DataContainer = {
items: [1, 2, 3],
data: ["a", "b"]
};
container.items.push(4); // ❌ 只读属性,不能修改
container.items[0] = 10; // ❌ 不能修改元素
四、多余属性检查(Excess Property Checks)
当使用对象字面量直接赋值给接口类型时,TypeScript 会检查是否有接口定义之外的属性。
typescript
interface User {
name: string;
age: number;
}
// 错误:对象字面量中的 email 不是 User 的已知属性
const user: User = {
name: "Alice",
age: 25,
email: "alice@example.com" // ❌ 多余属性
};
但如果先创建对象再赋值,多余属性不会被检查:
typescript
const obj = {
name: "Alice",
age: 25,
email: "alice@example.com"
};
const user: User = obj; // OK,没有多余属性检查
4.1 绕过多余属性检查的方法
方法一:使用类型断言
typescript
const user = {
name: "Alice",
age: 25,
email: "alice@example.com"
} as User; // 断言,绕过检查
方法二:添加索引签名
如果接口知道将来可能有额外属性,可以定义索引签名。
typescript
interface User {
name: string;
age: number;
[key: string]: unknown; // 允许任意额外属性
}
const user: User = {
name: "Alice",
age: 25,
email: "alice@example.com" // OK
};
方法三:先赋值给变量
如前所述,先将对象赋值给变量再传递给接口类型变量。
多余属性检查只在对象字面量直接赋值时生效,目的是捕获常见的拼写错误或属性名错误。
五、接口与类型别名的对比
interface 和 type 都可以定义对象形状,但各有侧重。
| 特性 | interface |
type |
|---|---|---|
| 定义对象形状 | ✅ | ✅ |
| 定义联合类型 | ❌ | ✅ |
| 定义元组 | ❌ | ✅ |
| 定义基本类型别名 | ❌ | ✅ |
| 声明合并 | ✅(同名接口自动合并) | ❌(同名声名会报错) |
| 继承/扩展 | extends 关键字 |
& 交叉类型 |
实现类(implements) |
✅ | ✅(但类实现接口更常见) |
| 性能 | 更友好(缓存、错误信息) | 某些复杂类型可能报错较长 |
5.1 声明合并
接口可以重复定义,TypeScript 会自动合并:
typescript
interface Box {
width: number;
}
interface Box {
height: number;
}
// 等价于
interface Box {
width: number;
height: number;
}
类型别名不能重复定义。
5.2 扩展方式对比
typescript
// 接口继承
interface Animal {
name: string;
}
interface Dog extends Animal {
bark(): void;
}
// 类型别名交叉
type Animal = { name: string };
type Dog = Animal & { bark(): void };
5.3 选择建议
- 定义对象形状(尤其是会被扩展或实现的)优先用
interface,因为声明合并和更清晰的错误信息。 - 定义联合类型、元组、基本类型别名、工具类型等用
type。 - 库的公共 API 推荐用
interface,方便用户通过声明合并扩展。
六、接口的函数类型和索引签名(简介)
接口不仅可以描述对象属性,还可以描述函数类型和索引签名。
6.1 函数类型接口
typescript
interface StringTransformer {
(input: string): string;
}
const toUpper: StringTransformer = (s) => s.toUpperCase();
6.2 索引签名
typescript
interface StringArray {
[index: number]: string;
}
const arr: StringArray = ["a", "b", "c"];
console.log(arr[0]); // "a"
索引签名可以是字符串或数字,用于描述动态属性。
typescript
interface Dictionary {
[key: string]: string;
}
const dict: Dictionary = {
hello: "world",
foo: "bar"
};
注意:数字索引的返回值类型必须是字符串索引返回值类型的子类型(因为 JavaScript 会将数字索引转换为字符串)。
七、常见错误与注意事项
7.1 忘记使用 = 定义接口
typescript
interface User = { name: string }; // ❌ 接口不需要等号
7.2 使用 interface 定义联合类型
typescript
interface Status = "on" | "off"; // ❌ 接口不能定义联合,用 type
7.3 只读属性与 const 混淆
readonly 表示属性本身不能重新赋值,但如果属性是对象,其内部属性仍可修改。
typescript
interface Config {
readonly settings: { level: number };
}
const cfg: Config = { settings: { level: 1 } };
cfg.settings.level = 2; // OK,内部可变
cfg.settings = { level: 3 }; // ❌ 不能重新赋值 settings
如需深层只读,可使用 Readonly<T> 工具类型或 as const。
7.4 多余属性检查意外触发
当函数参数直接传入对象字面量时会触发多余属性检查,提前设计可选属性或索引签名可避免。
typescript
function processUser(user: User) {}
// 触发多余属性检查
processUser({ name: "Alice", age: 25, email: "a@b.com" }); // ❌
// 不触发
const extra = { name: "Alice", age: 25, email: "a@b.com" };
processUser(extra); // OK
八、综合示例
typescript
// 定义用户接口
interface User {
readonly id: number;
name: string;
email?: string;
createdAt: Date;
}
// 定义管理员接口,继承 User
interface Admin extends User {
role: "admin";
permissions: string[];
}
// 普通用户
interface RegularUser extends User {
role: "user";
lastLogin?: Date;
}
type AppUser = Admin | RegularUser;
// 创建用户的工厂函数
function createUser(id: number, name: string, role: "admin" | "user"): AppUser {
const base = {
id,
name,
createdAt: new Date()
};
if (role === "admin") {
return {
...base,
role: "admin",
permissions: ["read", "write"]
};
} else {
return {
...base,
role: "user"
};
}
}
// 使用
const admin = createUser(1, "Alice", "admin");
console.log(admin.permissions); // ["read", "write"]
// admin.id = 2; // ❌ 只读属性
// 索引签名示例:配置对象
interface Settings {
theme: "light" | "dark";
fontSize: number;
[key: string]: unknown; // 允许其他扩展配置
}
const settings: Settings = {
theme: "dark",
fontSize: 14,
experimentalFeature: true, // OK
customKey: "value"
};
// 接口声明合并
interface Car {
brand: string;
}
interface Car {
model: string;
}
const myCar: Car = { brand: "Toyota", model: "Camry" };
九、小结
| 概念 | 语法示例 | 说明 |
|---|---|---|
| 接口定义 | interface User { name: string; } |
描述对象形状 |
| 可选属性 | age?: number |
属性可以不存在 |
| 只读属性 | readonly id: number |
创建后不可修改 |
| 多余属性检查 | 对象字面量直接赋值时触发 | 可用断言或索引签名绕过 |
| 函数类型接口 | { (x: number): string } |
描述函数类型 |
| 索引签名 | { [key: string]: unknown } |
描述动态属性 |
| 接口合并 | 同名接口自动合并 | 利于扩展 |
vs type |
对象形状优先用 interface |
联合/元组用 type |
觉得文章有帮助?别忘了:
👍 点赞 👍 -- 给我一点鼓励
⭐ 收藏 ⭐ -- 方便以后查看
🔔 关注 🔔 -- 获取更新通知
标签: #TypeScript #接口 #interface #对象形状 #学习笔记 #前端开发