对象是 JavaScript 中最重要的数据结构。TypeScript 提供了多种方式来描述对象的类型,确保对象属性的类型安全。
对象类型基础#
内联类型注解#
最直接的方式是在变量声明时内联定义对象类型:
// TypeScript 5.x
// 内联对象类型const user: { name: string; age: number } = { name: '张三', age: 25,}
// 函数参数使用对象类型function printUser(user: { name: string; age: number }) { console.log(`${user.name}, ${user.age}岁`)}
// 函数返回对象类型function createUser(): { id: number; name: string } { return { id: 1, name: '新用户' }}类型推断#
TypeScript 会自动推断对象字面量的类型:
// 自动推断为 { name: string; age: number; active: boolean }const person = { name: '张三', age: 25, active: true,}
// 推断的类型可以用于后续操作person.name = '李四' // ✅ 正确// person.email = 'test@test.com'; // ❌ 属性不存在可选属性#
使用 ? 标记可选属性:
// email 和 phone 是可选的const user: { name: string age: number email?: string phone?: string} = { name: '张三', age: 25, // email 和 phone 可以不提供}
// 可选属性的类型是 T | undefinedfunction greet(user: { name: string; title?: string }) { if (user.title) { console.log(`${user.title} ${user.name}`) } else { console.log(user.name) }}
greet({ name: '张三' }) // 张三greet({ name: '张三', title: '博士' }) // 博士 张三🔶 可选属性的类型实际上是 T | undefined,访问前需要检查:
function getEmailLength(user: { email?: string }): number { // return user.email.length; // ❌ 错误:email 可能是 undefined
// 方式1:条件检查 if (user.email) { return user.email.length } return 0
// 方式2:可选链 return user.email?.length ?? 0
// 方式3:非空断言(确定不为空时) return user.email!.length // 谨慎使用}只读属性#
使用 readonly 标记只读属性:
const config: { readonly apiUrl: string readonly timeout: number retries: number // 可修改} = { apiUrl: 'https://api.example.com', timeout: 5000, retries: 3,}
// config.apiUrl = 'xxx'; // ❌ 错误:只读属性config.retries = 5 // ✅ 正确🤔 readonly 是浅层的:
const user: { readonly info: { name: string; age: number }} = { info: { name: '张三', age: 25 },}
// user.info = { name: '李四', age: 30 }; // ❌ 不能重新赋值user.info.name = '李四' // ✅ 可以修改嵌套属性!索引签名#
当对象有动态的属性名时,使用索引签名:
// 字符串索引签名const scores: { [key: string]: number } = { math: 90, english: 85, physics: 88,}
scores['chemistry'] = 92 // ✅ 可以添加新属性console.log(scores['math']) // 90
// 数字索引签名(类似数组)const items: { [index: number]: string } = { 0: 'first', 1: 'second', 2: 'third',}索引签名与固定属性#
// 索引签名可以和固定属性混用interface Dictionary { length: number // 固定属性 [key: string]: number // 索引签名}
const dict: Dictionary = { length: 3, a: 1, b: 2, c: 3,}
// 🔶 注意:固定属性的类型必须兼容索引签名的类型interface InvalidDict { name: string // ❌ 错误:string 不能赋给 number [key: string]: number}
// ✅ 正确:使用联合类型interface ValidDict { name: string [key: string]: string | number}Record 工具类型#
Record<K, V> 是更简洁的索引签名语法:
// 等价于 { [key: string]: number }const scores: Record<string, number> = { math: 90, english: 85,}
// 限定键的类型type Subject = 'math' | 'english' | 'physics'const grades: Record<Subject, number> = { math: 90, english: 85, physics: 88,}嵌套对象#
// 嵌套对象类型const company: { name: string address: { city: string street: string zipCode: string } employees: { name: string role: string }[]} = { name: 'Acme Inc', address: { city: '北京', street: '中关村大街1号', zipCode: '100000', }, employees: [ { name: '张三', role: '工程师' }, { name: '李四', role: '设计师' }, ],}🎯 对于复杂的嵌套结构,建议拆分成多个类型:
// 拆分类型定义(更清晰)type Address = { city: string street: string zipCode: string}
type Employee = { name: string role: string}
type Company = { name: string address: Address employees: Employee[]}
const company: Company = { name: 'Acme Inc', address: { city: '北京', street: '中关村大街1号', zipCode: '100000', }, employees: [ { name: '张三', role: '工程师' }, { name: '李四', role: '设计师' }, ],}对象类型的结构化类型#
TypeScript 使用结构化类型系统(鸭子类型):只要结构匹配,类型就兼容。
type Point = { x: number y: number}
// 对象可以有额外属性const point3D = { x: 1, y: 2, z: 3 }
function printPoint(p: Point) { console.log(`(${p.x}, ${p.y})`)}
// ✅ 正确:point3D 有 x 和 y 属性printPoint(point3D)🔶 但对象字面量会有额外属性检查:
type Point = { x: number; y: number }
// ❌ 对象字面量直接赋值时会检查额外属性const p: Point = { x: 1, y: 2, z: 3 } // 错误:z 不在类型中
// ✅ 通过变量赋值可以绕过检查const temp = { x: 1, y: 2, z: 3 }const p2: Point = temp // 正确
// ✅ 或使用类型断言const p3 = { x: 1, y: 2, z: 3 } as Point空对象类型#
// 空对象类型 {} 表示"非 null 和 undefined 的任意值"let obj: {} = { name: '张三' }obj = 'hello' // ✅ 字符串也行obj = 123 // ✅ 数字也行obj = [] // ✅ 数组也行// obj = null; // ❌ 错误// obj = undefined; // ❌ 错误
// 真正的空对象应该用其他方式表示type EmptyObject = Record<string, never>// 或type EmptyObject2 = { [key: string]: never }object 类型#
object 类型表示非原始类型的值:
// object:非原始类型(不是 string、number、boolean 等)let obj: object
obj = { name: '张三' } // ✅obj = [1, 2, 3] // ✅obj = () => {} // ✅
// obj = 'hello'; // ❌ 原始类型// obj = 123; // ❌ 原始类型// obj = true; // ❌ 原始类型
// 🔶 object 类型无法访问具体属性const o: object = { name: '张三' }// console.log(o.name); // ❌ 错误🤔 object vs Object vs {}:
| 类型 | 含义 | 可接受的值 |
|---|---|---|
object | 非原始类型 | 对象、数组、函数 |
Object | 所有有 Object 方法的值 | 几乎所有值(不推荐使用) |
{} | 非 null/undefined 的任意值 | 几乎所有值 |
实际应用#
API 响应类型#
// API 响应的通用结构type ApiResponse<T> = { code: number message: string data: T timestamp: number}
// 用户数据type User = { id: number name: string email: string createdAt: string}
// 使用const response: ApiResponse<User> = { code: 200, message: 'success', data: { id: 1, name: '张三', email: 'zhangsan@example.com', createdAt: '2024-01-01', }, timestamp: Date.now(),}配置对象#
type DatabaseConfig = { readonly host: string readonly port: number readonly database: string username?: string password?: string options?: { ssl?: boolean timeout?: number }}
const dbConfig: DatabaseConfig = { host: 'localhost', port: 5432, database: 'myapp', options: { ssl: true, },}表单数据#
type FormData = { [field: string]: string | number | boolean | undefined name: string email: string age?: number newsletter?: boolean}
function validateForm(data: FormData): boolean { return data.name.length > 0 && data.email.includes('@')}常见问题#
🙋 什么时候用 type,什么时候用 interface?#
两者在定义对象类型时基本等价。一般建议:
- 定义对象结构:用
interface(可扩展) - 定义联合类型、元组等:用
type - 项目统一风格最重要
// 两者效果相同interface User1 { name: string age: number}
type User2 = { name: string age: number}🙋 如何让所有属性变成可选?#
使用 Partial<T> 工具类型:
type User = { name: string age: number email: string}
type PartialUser = Partial<User>// 等价于 { name?: string; age?: number; email?: string }
const update: PartialUser = { name: '新名字' }🙋 如何让所有属性变成只读?#
使用 Readonly<T> 工具类型:
type User = { name: string age: number}
type ReadonlyUser = Readonly<User>// 等价于 { readonly name: string; readonly age: number }
const user: ReadonlyUser = { name: '张三', age: 25 }// user.name = '李四'; // ❌ 错误总结#
| 特性 | 语法 | 用途 |
|---|---|---|
| 对象类型 | { prop: type } | 描述对象结构 |
| 可选属性 | prop?: type | 属性可以不存在 |
| 只读属性 | readonly prop: type | 属性不可修改 |
| 索引签名 | { [key: string]: type } | 动态属性名 |
| Record | Record<K, V> | 简洁的索引类型 |
下一篇我们将学习函数类型,了解如何为函数添加完整的类型注解。