对象类型
1. 对象类型定义
基本语法
匿名对象类型:直接在函数参数中声明。
function greet(person: { name: string; age: number }) { return "Hello " + person.name; }
接口(Interface):命名复用性强的对象类型。
interface Person { name: string; age: number; } function greet(person: Person) { return "Hello " + person.name; }
类型别名(Type Alias):适用于更复杂的类型组合。
type Person = { name: string; age: number; }; function greet(person: Person) { return "Hello " + person.name; }
差异比较
- 接口 vs 类型别名:
- 接口:支持合并(相同名称的接口会自动合并),适合定义契约。
- 类型别名:一次性定义,不能重复声明,适合联合类型(
type ID = string | number
)。
2. 属性修饰符
可选属性
使用
?
标记属性为可选,调用时可省略。interface PaintOptions { shape: Shape; xPos?: number; yPos?: number; } paintShape({ shape }); // 合法 paintShape({ shape, xPos: 100 }); // 合法
默认值处理:
使用解构赋值设置默认值,简化代码。
function paintShape({ shape, xPos = 0, yPos = 0 }: PaintOptions) { console.log("x coordinate at", xPos); }
只读属性
使用
readonly
防止属性被修改。interface SomeType { readonly prop: string; } const obj: SomeType = { prop: "value" }; obj.prop = "new value"; // ❌ 错误:不可写
嵌套对象的只读性:
readonly
只限制顶层属性,不影响内部对象的可变性。interface Home { readonly resident: { name: string; age: number }; } let home: Home = { resident: { name: "Alice", age: 30 } }; home.resident.age++; // ✅ 允许修改内部属性 home.resident = { ... }; // ❌ 不允许重新赋值
3. 索引签名
动态键值对
描述未知属性名的对象。
interface StringArray { [index: number]: string; } const arr: StringArray = ["a", "b"]; // ✅ console.log(arr[1]); // 输出 "b"
字符串索引签名:
限制所有属性值类型一致,适用于字典模式。
interface Dictionary { [key: string]: number; } const dict: Dictionary = { a: 1, b: 2 }; // ✅
混合固定属性与索引签名:
固定属性类型必须是索引签名类型的子集。
interface NumberOrStringDictionary { [key: string]: number | string; length: number; // ✅ 允许 name: string; // ✅ 允许 }
只读索引签名
防止通过索引修改值。
interface ReadonlyStringArray { readonly [index: number]: string; } let arr: ReadonlyStringArray = ["a", "b"]; arr[0] = "c"; // ❌ 错误:不可修改
4. 超额属性检查
严格检查机制
创建对象字面量时,若包含目标类型未声明的属性,TypeScript 报错。
interface SquareConfig { color?: string; width?: number; } createSquare({ colour: "red", width: 100 }); // ❌ 'colour' 不存在于 SquareConfig 中
绕过检查的方法
类型断言(需谨慎使用):
createSquare({ colour: "red", width: 100 } as SquareConfig);
添加字符串索引签名:
interface SquareConfig { [propName: string]: unknown; // 允许任意属性 color?: string; width?: number; }
中间变量赋值:
const options = { colour: "red", width: 100 }; createSquare(options); // ✅ 无错误
5. 类型扩展与交叉
接口扩展
使用
extends
继承并扩展其他接口。interface BasicAddress { street: string; city: string; } interface FullAddress extends BasicAddress { postalCode: string; }
交叉类型(Intersection Types)
使用
&
合并多个类型。interface Colorful { color: string; } interface Circle { radius: number; } type ColorfulCircle = Colorful & Circle; const cc: ColorfulCircle = { color: "red", radius: 42, };
6. 工具类型
常用工具类型
Partial:将所有属性变为可选。
type PartialPerson = Partial<Person>; // name?: string; age?: number
Required:将所有属性变为必填。
type RequiredPerson = Required<Person>; // name: string; age: number
Readonly:将所有属性设为只读。
type ReadOnlyPerson = Readonly<Person>;
7. 实际应用场景
表单数据处理
动态表单字段类型化:
interface FormData { [fieldName: string]: string | number | boolean | undefined; } function processForm(data: FormData) { Object.entries(data).forEach(([key, value]) => { if (typeof value === "string") { console.log(`${key}: ${value}`); } }); }
缓存实现
使用索引签名管理缓存数据:
interface Cache<T> { [key: string]: { value: T; timestamp: number }; } class DataCache<T> { private cache: Cache<T> = {}; set(key: string, value: T): void { this.cache[key] = { value, timestamp: Date.now() }; } get(key: string): T | undefined { return this.cache[key]?.value; } }
8. 最佳实践
- 优先使用接口:定义明确的契约,便于扩展和维护。
- 合理使用可选属性:避免强制要求不必要的字段。
- 避免过度使用索引签名:可能导致类型不安全,优先考虑
Map
或Record
。 - 谨慎使用类型断言:确保类型正确性,避免隐藏错误。
- 利用工具类型:减少重复代码,提升类型安全性。
9. 高级技巧
泛型对象类型
结合泛型实现灵活的类型约束:
interface ApiResponse<T> { data: T; meta: { [key: string]: unknown }; } async function fetchData<T>(url: string): Promise<ApiResponse<T>> { const response = await fetch(url); return response.json(); }
映射类型
自定义映射类型转换属性:
type Optional<T> = { [K in keyof T]?: T[K]; }; type OptionalPerson = Optional<Person>; // name?: string; age?: number
10. 常见误区
索引签名与固定属性冲突:
固定属性类型必须符合索引签名类型,否则报错。
interface User { [key: string]: string | number; // ❌ 下方的布尔值不符合 isAdmin: boolean; }
只读属性与不可变性的区别:
readonly
仅限制属性本身不可重写,不影响内部对象的修改。