Skip to content

对象类型

概述

在 TypeScript 中,对象类型(Object Types)用于描述对象的结构和属性。对象类型是 TypeScript 类型系统的重要组成部分,它允许我们定义对象应该具有哪些属性,以及每个属性的类型。通过对象类型,我们可以在编译时检查对象的结构是否正确,避免运行时错误。

基本语法

对象类型使用花括号 {} 定义,其中包含属性名和类型:

typescript
// 基本对象类型定义
let user: {
  name: string;
  age: number;
  email: string;
};

// 使用对象类型
user = {
  name: "John Doe",
  age: 30,
  email: "john@example.com"
};

对象类型特性

必需属性

默认情况下,对象类型中定义的所有属性都是必需的,必须提供且类型匹配:

typescript
// 定义对象类型
type Person = {
  name: string;
  age: number;
};

// ✅ 正确:提供所有必需属性
const person1: Person = {
  name: "Alice",
  age: 25
};

// ❌ 错误:缺少必需属性 age
const person2: Person = {
  name: "Bob"
  // Error: Property 'age' is missing in type '{ name: string; }'
};

// ❌ 错误:类型不匹配
const person3: Person = {
  name: "Charlie",
  age: "30"  // Error: Type 'string' is not assignable to type 'number'
};

可选属性

使用 ? 标记属性为可选,表示该属性可以存在也可以不存在:

typescript
// 定义包含可选属性的对象类型
type User = {
  name: string;
  age: number;
  email?: string;  // 可选属性
  phone?: string;  // 可选属性
};

// ✅ 正确:提供所有必需属性
const user1: User = {
  name: "Alice",
  age: 25
};

// ✅ 正确:提供部分可选属性
const user2: User = {
  name: "Bob",
  age: 30,
  email: "bob@example.com"
};

// ✅ 正确:提供所有属性
const user3: User = {
  name: "Charlie",
  age: 35,
  email: "charlie@example.com",
  phone: "123-456-7890"
};

只读属性

使用 readonly 关键字标记属性为只读,表示该属性在创建后不能被修改:

typescript
// 定义包含只读属性的对象类型
type Config = {
  readonly apiKey: string;
  readonly version: string;
  timeout: number;  // 普通属性,可以修改
};

// 创建对象
const config: Config = {
  apiKey: "abc123",
  version: "1.0.0",
  timeout: 5000
};

// ✅ 正确:可以修改普通属性
config.timeout = 10000;

// ❌ 错误:不能修改只读属性
config.apiKey = "new-key";  // Error: Cannot assign to 'apiKey' because it is a read-only property
config.version = "2.0.0";   // Error: Cannot assign to 'version' because it is a read-only property

提示

readonly 只在编译时生效,运行时仍然可以修改。如果需要真正的不可变对象,可以使用 Object.freeze() 或第三方库。

索引签名

当对象的属性名不确定时,可以使用索引签名(Index Signature)来定义:

typescript
// 使用索引签名定义对象类型
type Dictionary = {
  [key: string]: string;  // 索引签名:所有键都是 string,值也是 string
};

const dict: Dictionary = {
  "apple": "苹果",
  "banana": "香蕉",
  "cherry": "樱桃"
};

// 可以动态添加属性
dict["date"] = "日期";

// ❌ 错误:值类型不匹配
dict["number"] = 123;  // Error: Type 'number' is not assignable to type 'string'

索引签名也可以与其他属性结合使用:

typescript
// 混合使用固定属性和索引签名
type UserData = {
  name: string;           // 固定属性
  age: number;            // 固定属性
  [key: string]: string | number;  // 索引签名:允许其他 string 或 number 类型的属性
};

const userData: UserData = {
  name: "John",
  age: 30,
  city: "Beijing",       // 通过索引签名允许
  score: 95              // 通过索引签名允许
};

注意

当使用索引签名时,固定属性的类型必须是索引签名类型的子类型。例如,如果索引签名是 [key: string]: string | number,那么所有固定属性的类型必须是 string | number 或其子类型。

使用示例

示例 1:用户信息管理

typescript
// 定义用户对象类型
type User = {
  id: number;
  name: string;
  email: string;
  age?: number;           // 可选属性
  readonly createdAt: Date;  // 只读属性
  preferences?: {
    theme: "light" | "dark";
    language: string;
  };
};

// 创建用户对象
function createUser(
  id: number,
  name: string,
  email: string,
  age?: number
): User {
  return {
    id,
    name,
    email,
    age,
    createdAt: new Date()  // 只读属性在创建时设置
  };
}

// 使用示例
const user = createUser(1, "Alice", "alice@example.com", 25);
console.log(user.name);        // "Alice"
console.log(user.age);         // 25
console.log(user.createdAt);   // 当前日期

// 可以添加可选属性
user.preferences = {
  theme: "dark",
  language: "zh-CN"
};

示例 2:配置对象

typescript
// 定义应用配置类型
type AppConfig = {
  readonly appName: string;
  readonly version: string;
  api: {
    baseUrl: string;
    timeout: number;
    retries?: number;  // 可选的重试次数
  };
  features: {
    [featureName: string]: boolean;  // 使用索引签名表示动态特性开关
  };
};

// 创建配置对象
const config: AppConfig = {
  appName: "MyApp",
  version: "1.0.0",
  api: {
    baseUrl: "https://api.example.com",
    timeout: 5000,
    retries: 3
  },
  features: {
    darkMode: true,
    notifications: false,
    analytics: true
  }
};

// 可以动态添加特性
config.features["newFeature"] = true;

// ❌ 错误:不能修改只读属性
// config.appName = "NewApp";  // Error

示例 3:表单数据验证

typescript
// 定义表单数据类型
type FormData = {
  username: string;
  email: string;
  password: string;
  confirmPassword?: string;  // 可选属性
  rememberMe: boolean;
};

// 验证函数
function validateForm(data: FormData): boolean {
  // 检查必需字段
  if (!data.username || !data.email || !data.password) {
    return false;
  }

  // 验证邮箱格式(简单示例)
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!emailRegex.test(data.email)) {
    return false;
  }

  // 如果提供了确认密码,检查是否匹配
  if (data.confirmPassword && data.password !== data.confirmPassword) {
    return false;
  }

  return true;
}

// 使用示例
const formData: FormData = {
  username: "john_doe",
  email: "john@example.com",
  password: "secret123",
  confirmPassword: "secret123",
  rememberMe: true
};

if (validateForm(formData)) {
  console.log("表单验证通过");
} else {
  console.log("表单验证失败");
}

对象类型与接口

对象类型可以使用类型别名(Type Alias)或接口(Interface)来定义。两者在大多数情况下可以互换使用:

typescript
// 使用类型别名定义对象类型
type Point = {
  x: number;
  y: number;
};

// 使用接口定义对象类型
interface PointInterface {
  x: number;
  y: number;
}

// 两者使用方式相同
const point1: Point = { x: 10, y: 20 };
const point2: PointInterface = { x: 10, y: 20 };

信息

关于类型别名和接口的区别,将在接口类型别名章节中详细讨论。

嵌套对象类型

对象类型可以嵌套,用于表示复杂的数据结构:

typescript
// 定义嵌套对象类型
type Address = {
  street: string;
  city: string;
  zipCode: string;
  country: string;
};

type Company = {
  name: string;
  address: Address;  // 嵌套对象类型
  employees: number;
};

type Employee = {
  id: number;
  name: string;
  email: string;
  company: Company;  // 嵌套对象类型
  address?: Address;  // 可选嵌套对象
};

// 使用嵌套对象类型
const employee: Employee = {
  id: 1,
  name: "Alice",
  email: "alice@example.com",
  company: {
    name: "Tech Corp",
    address: {
      street: "123 Main St",
      city: "San Francisco",
      zipCode: "94102",
      country: "USA"
    },
    employees: 100
  },
  address: {
    street: "456 Oak Ave",
    city: "San Francisco",
    zipCode: "94103",
    country: "USA"
  }
};

类型检查示例

常见错误

typescript
// ❌ 错误:缺少必需属性
type User = {
  name: string;
  age: number;
};

const user: User = {
  name: "John"
  // Error: Property 'age' is missing in type '{ name: string; }'
};

// ❌ 错误:类型不匹配
const user2: User = {
  name: "John",
  age: "30"  // Error: Type 'string' is not assignable to type 'number'
};

// ❌ 错误:多余的属性(在严格模式下)
const user3: User = {
  name: "John",
  age: 30,
  email: "john@example.com"  // Error: Object literal may only specify known properties
};

// ❌ 错误:修改只读属性
type Config = {
  readonly apiKey: string;
};

const config: Config = {
  apiKey: "abc123"
};

config.apiKey = "new-key";  // Error: Cannot assign to 'apiKey' because it is a read-only property

正确写法

typescript
// ✅ 正确:提供所有必需属性
type User = {
  name: string;
  age: number;
  email?: string;  // 可选属性
};

const user: User = {
  name: "John",
  age: 30
};

// ✅ 正确:提供可选属性
const user2: User = {
  name: "John",
  age: 30,
  email: "john@example.com"
};

// ✅ 正确:使用索引签名允许额外属性
type FlexibleUser = {
  name: string;
  age: number;
  [key: string]: string | number;
};

const user3: FlexibleUser = {
  name: "John",
  age: 30,
  email: "john@example.com",  // 通过索引签名允许
  score: 95
};

注意事项

提示

  • 对象类型是结构化的(Structural Typing),只要对象的结构匹配类型定义,就可以赋值,不需要显式声明类型
  • 使用可选属性 ? 时,在访问属性前应该检查属性是否存在,避免运行时错误
  • 只读属性 readonly 只在编译时生效,运行时仍然可以修改(需要使用 Object.freeze() 实现真正的不可变)

注意

  • 在严格模式下,对象字面量不能包含未在类型定义中声明的属性(除非使用索引签名)
  • 索引签名的类型必须包含所有固定属性的类型,否则会出现类型错误
  • 可选属性在访问时可能是 undefined,需要进行空值检查

重要

  • 对象类型是 TypeScript 类型系统的基础,理解对象类型对于学习接口、类型别名等高级概念非常重要
  • 对象类型检查发生在编译时,不会影响运行时的性能
  • 使用对象类型可以大大提高代码的可维护性和可读性

相关链接

基于 VitePress 构建