TypeScript 中的对象详解
在 TypeScript 中,对象 是最常见的数据结构之一。TypeScript 通过强大的类型系统来描述对象的形状(shape),确保对象属性存在、类型正确,从而大幅减少运行时错误。
1. 对象字面量与类型推断
let user = {
name: "Alice",
age: 30,
isAdmin: true
};
// 类型推断为:{ name: string; age: number; isAdmin: boolean; }
user.name = "Bob"; // OK
// user.age = "30"; // 错误:类型不匹配
// user.role = "admin"; // 错误:对象上不存在 role 属性
2. 使用接口(interface)定义对象类型(推荐)
interface User {
name: string;
age: number;
isAdmin?: boolean; // 可选属性
readonly id: number; // 只读属性
}
let admin: User = {
name: "Eve",
age: 28,
id: 1
// isAdmin 可省略
};
// admin.id = 2; // 错误:只读属性
admin.age = 29; // OK
3. 使用类型别名(type)定义对象类型
type Point = {
x: number;
y: number;
z?: number; // 可选
};
let origin: Point = { x: 0, y: 0 };
// origin.z = undefined; // OK,可选属性可为 undefined
4. 匿名对象类型
直接在变量声明时定义(适合临时使用):
let config: {
apiUrl: string;
timeout: number;
readonly debug: boolean;
} = {
apiUrl: "https://api.example.com",
timeout: 5000,
debug: true
};
5. 索引签名(Index Signatures)—— 动态属性
当对象属性名不确定时使用:
interface StringMap {
[key: string]: string; // 任意 string 键,值必须是 string
}
let headers: StringMap = {
"Content-Type": "application/json",
"Authorization": "Bearer token123"
};
headers["X-Custom"] = "value"; // OK
// headers.age = 30; // 错误:值必须是 string
也可以限制键为 number:
interface NumberArray {
[index: number]: string; // 如数组,但值必须是 string
}
6. 额外属性检查(Excess Property Checks)
TypeScript 对对象字面量有严格检查:
interface Person {
name: string;
age: number;
}
let p: Person = {
name: "Tom",
age: 25,
role: "admin" // 错误:role 不存在于 Person 接口
};
// 但通过变量赋值可绕过(结构化类型系统):
let extra = { name: "Tom", age: 25, role: "admin" };
let p2: Person = extra; // OK,多余属性被允许
7. 对象展开与合并
let defaults = { timeout: 3000, retries: 3 };
let config = { ...defaults, timeout: 5000 }; // timeout 被覆盖
// 类型:{ timeout: number; retries: number; }
8. 内置工具类型(Utility Types)操作对象
TypeScript 提供许多实用类型来变换对象类型:
| 工具类型 | 作用 | 示例 |
|---|---|---|
Partial<T> | 所有属性变为可选 | Partial<User> → name?, age?, isAdmin? |
Required<T> | 所有属性变为必选 | Required<User> |
Readonly<T> | 所有属性变为只读 | Readonly<User> |
Pick<T, K> | 挑选指定属性 | Pick<User, "name" | "age"> |
Omit<T, K> | 排除指定属性 | Omit<User, "id"> |
Record<K, T> | 创建键为 K、值为 T 的对象类型 | Record<string, number> |
示例:
type UserUpdate = Partial<User>; // 更新时所有字段可选
let update: UserUpdate = { name: "New Name" }; // OK,只改 name
type UserBasic = Pick<User, "name" | "age">;
let basic: UserBasic = { name: "Alice", age: 30 };
9. 对象 vs Map
| 场景 | 推荐使用 |
|---|---|
| 键为字符串,结构固定 | 对象 + interface |
| 键为任意类型(对象、Symbol) | Map |
| 需要动态添加属性 | 对象 + 索引签名 或 Map |
10. 最佳实践建议
| 建议 | 说明 |
|---|---|
| 始终为对象定义接口或 type | 提升可读性和安全性 |
可选属性用 ?,只读用 readonly | 明确意图 |
| 动态对象用索引签名 | 避免 any |
| 使用工具类型改造对象 | 如 Partial 用于更新函数参数 |
| 避免 any 类型对象 | 用 unknown 或具体接口替代 |
开启 strict 模式 | 包括 noImplicitAny、strictNullChecks 等 |
小结:对象类型常见写法速查
| 场景 | 推荐写法 |
|---|---|
| 固定结构对象 | interface User { name: string; age: number; } |
| 可选/只读属性 | isActive?: boolean; readonly id: number; |
| 动态键对象 | [key: string]: string; |
| 更新对象 | Partial<User> |
| 只取部分字段 | Pick<User, "name" | "age"> |
| 排除字段 | Omit<User, "password"> |
对象是 TypeScript 中最核心的数据载体,通过接口、工具类型和类型推断的结合,能实现高度类型安全的代码。
如果您想深入某个部分(如嵌套对象、对象解构与类型、对象合并的高级类型、或实际项目中的对象设计),请告诉我!