发布于 2026-01-06 3 阅读
0

TypeScript 中的对象类型简介

TypeScript 中的对象类型简介

在 JavaScript 中,对象是处理和传递数据最常用的方式之一。在 TypeScript 中,有一种专门为对象创建的特殊类型,称为“对象类型”。本教程将帮助你理解 TypeScript 中的对象类型是什么以及如何使用它们。

对象类型简述

在 JavaScript 中,值基本上分为两类。第一类是基本数据类型,也就是八种基本数据类型,其中一些你会经常用到。这些数据类型包括字符串、数字、布尔值、null、符号等等。除了这些基本数据类型之外,还有第二类值。

第二类值是对象。在 JavaScript 中,你可以通过查看值本身来快速区分原始数据类型和对象。如果值本身有任何属性,那么它就是一个对象。否则,它就是八种原始数据类型之一。每种类型在 TypeScript 中都有对应的类型。

这同样适用于对象。在 TypeScript 中,新增了一种名为对象类型的类型。这种类型适用于任何至少具有一个属性的值。这种新类型旨在简化对象操作以及对其进行注解的过程。

匿名对象类型

TypeScript 允许你定义两种类型的对象。第一种是匿名对象。匿名对象是指在不使用类型或接口的情况下,为特定对象定义一个对象。函数参数就是一个匿名对象的例子。假设你有一个函数,它接受一个对象作为参数。

如果要将此对象参数的对象类型定义为匿名对象,则需要在函数定义处进行定义。您需要定义对象应具有的属性。对于每个属性,您还需要定义属性值的类型。

// Define a function with anonymous object type:
function myFunc(user: { username: string, email: string }) {
  return `user: ${user.username}, email: ${user.email}`
}
Enter fullscreen mode Exit fullscreen mode

在上面的示例中,您定义了一个名为 `the` 的对象参数user。该参数的匿名对象类型表明该对象有两个属性:` usernameand` 和 ` email.`。这两个属性均为字符串类型,且均为必需属性。

命名对象类型

定义对象类型的第二种方法是使用类型别名或接口。在这种情况下,您可以使用其中之一来定义对象的形状。当您想使用此形状来注解对象时,需要引用类型别名或接口。TypeScript 将使用该别名或接口来推断所有对象属性的类型。

// No.1: type alias
// Create a type alias for user object:
type User = {
  username: string;
  email: string;
}

// No.2: interface
// Create am interface for user object:
interface User {
  username: string;
  email: string;
}

// Use the type alias or interface to annotate user parameter:
function myFunc(user: User) {
  return `user: ${user.username}, email: ${user.email}`
}
Enter fullscreen mode Exit fullscreen mode

对象类型本身的结构保持不变。它仍然包含两个字符串类型的属性。区别在于,现在对象类型是在函数或使用它的位置之外定义的,而且可以根据需要独立于函数或位置进行定义。

命名对象和匿名对象类型及可重用性

命名对象类型的一大优势在于代码的可重用性。将对象类型定义为命名类型后,您可以根据需要多次使用它们。如果您还导出它们,则可以在任何地方使用它们。只需编写一次,即可随时随地使用。匿名类型无法做到这一点。

// Define the type alias for Human object once:
type Human = {
    name: string;
    age: number;
}

// Use Human type alias for one function:
function getUser(user: Human) {
  return `name: ${user.name}, age: ${user.age}`
}

getUser({ name: 'Tim', age: 44 })
// Output:
// 'name: Tim, age: 44'

// Use Human type alias for another function:
function getUsers(users: Human[]) {
  const usersNames = users.map(user => user.name)

  return usersNames
}

getUsers([{
  name: 'Joe',
  age: 21
}, {
  name: 'Jack',
  age: 36
}, {
  name: 'Samantha',
  age: 27
}])
// Output:
// [ 'Joe', 'Jack', 'Samantha' ]
Enter fullscreen mode Exit fullscreen mode

由于匿名对象类型没有名称,因此无法在代码的其他地方引用它。如果想要重用它定义的结构,则必须重新编写。这也是 TypeScript 开发者更倾向于使用命名对象类型而非匿名对象类型的原因之一。然而,这并不意味着永远不应该使用匿名对象类型。

一个好的经验法则是,先考虑对象本身,以及你再次使用其形状的可能性。如果你很可能需要用到它的形状或类似的形状,那么创建一个类型别名或接口可能是一个好主意。这样,每当你需要用到这个特定形状时,就可以引用这个别名或接口。

这样一来,您在开发过程中进行修改就容易得多。您只需修改一个地方,即别名或接口。修改完成后,所有使用该别名或接口的地方都会自动更新。相比之下,您需要在代码中查找所有出现该特定形状的地方并进行更新。

这也有助于将 bug 的概率降至最低。当您更新别名或接口时,如果需要修改某些代码以反映新的结构,TypeScript 会立即发出警告。匿名对象类型则不会出现这种情况,因为 TypeScript 无法使用统一的数据源。

另一方面,如果您不太可能再次使用该形状或类似形状,则匿名对象类型即可满足需求。

对象类型和属性修饰符

定义对象类型(无论是匿名对象还是命名对象)时,所有属性都是必需的且可修改的。TypeScript 允许你借助属性修饰符来更改这些属性。

可选对象属性

对象可以拥有某些属性和对象必须拥有某些属性之间是有区别的。当你创建一个指定了某些属性的对象类型时,TypeScript 会期望在你使用该对象类型注解的对象中找到这些属性。

如果你忘记在对象中定义所有这些属性,TypeScript 会报错。此外,TypeScript 只期望找到你定义的属性,不会期望找到任何其他属性。如果找到了额外的属性,它也会报错。有两种方法可以解决这个问题。

第一种方法是创建对象类型的多个变体来涵盖各种使用场景。当你改变对象的形状时,这种方法或许可行。然而,仅仅为了让一个属性成为可选属性而创建一个新的变体,简直是荒谬至极。你可以做的是直接告诉 TypeScript 某个属性是可选的。

这样也能告诉 TypeScript 该属性可能未定义。如果确实未定义,TypeScript 应该会报错。当然,除非你实际尝试使用该属性。你可以通过?在对象类型中属性名称后紧跟问号 ( ) 来实现属性可选。

// Create object type with optional properties:
type Animal = {
  name: string;
  species: string;
  numberOfLegs?: number; // This property is optional (the '?' after the property name)
  wingSpan?: number; // This property is optional (the '?' after the property name)
  lengthOfTail?: number; // This property is optional (the '?' after the property name)
}

// This will work:
const dog: Animal = {
  name: 'Jack',
  species: 'dog',
  numberOfLegs: 4,
  lengthOfTail: 30
}

// This will work:
const bird: Animal = {
  name: 'Dorris',
  species: 'pelican',
  wingSpan: 1.83,
}

// This will work:
const fish: Animal = {
  name: 'Nemo',
  species: 'fish'
}

// This will not work:
const spider: Animal = {
  name: 'Joe'
  // The "species" property is required, but missing.
}
// TS error: Property 'species' is missing in type '{ name: string; }' but required in type 'Animal'.
Enter fullscreen mode Exit fullscreen mode

只读对象属性

第二个属性修饰符是 `readonly` readonly。此修饰符用于指定初始化后不应更改的属性值。请注意,此修饰符仅在 TypeScript 中有效。如果您将某个属性标记为只读,之后尝试更改它,TypeScript 将抛出错误。

然而,这并不能阻止 JavaScript 执行该更改。对于 JavaScript 来说,目前还没有真正意义上的只读属性。您可以通过readonly在对象类型中将属性名放在属性名之前,并加上 `readonly` 关键字来将其指定为只读属性。

// Create object type with optional properties:
type User = {
  readonly name: string; // Make "name" readonly
  readonly email: string; // Make "email" readonly
  password: string;
  role: 'admin' | 'user' | 'guest';
}

// This will work:
const jack: User = {
  name: 'Jack',
  email: 'jack@jack.com',
  password: '1234_some_pass_to_test_56789',
  role: 'guest'
}

// This will work:
// Try to change value of property "role" on "jack" object:
jack.role = 'user'

// This will not work:
// Try to change value of readonly property "email" on "jack" object:
jack.email = 'jack@yo.ai'
// TS error: Cannot assign to 'email' because it is a read-only property.
Enter fullscreen mode Exit fullscreen mode

对象类型和索引签名

到目前为止,我们处理的对象都是预先知道所有属性的。但这种情况并非总是如此。你可能会遇到这样的情况:你只知道属性的类型和预期值的类型,但可能不知道属性的确切名称。

在 TypeScript 中,由于索引签名的存在,这不成问题。通过索引签名,您可以同时指定属性的类型和其值的类型。这赋予了您极大的灵活性,因为只要两种类型都正确,TypeScript 就不会报错。

当你想使用索引签名时,需要记住使用略有不同的语法来定义属性。通常,你会定义一个属性“X”,添加冒号,然后为其值指定类型。这会告诉 TypeScript 对象中存在一个特定的属性“X”。问题是,我们并不知道这个“X”是什么。

要使用索引签名来解决这个问题,你需要用方括号将属性括起来,并添加类型声明。这个类型声明了属性本身的类型。索引签名允许的类型是字符串和数字。其余部分与索引签名相同。接下来是冒号和值的类型声明。

// Create object type with index signature:
type StringKey = {
  // The property will be a type of string:
  [key: string]: string;
}

// Create another object type with index signature:
type NumberKey = {
  // The property will be a type of number:
  [index: number]: string;
}

// This will work:
const user: StringKey = {
  // Property is always a string.
  firstName: 'Jack',
  lastName: 'Doe',
}

// This will work:
const bookshelf: StringKey = {
  // Property is always a number.
  1: 'Hackers and Painters',
  2: 'Blitzscaling',
}

// This will not work:
const languages: NumberKey = {
  // Properties are strings, not numbers.
  one: 'JavaScript',
  two: 'TypeScript',
}
// TS error: Type '{ one: string; two: string; }' is not assignable to type 'NumberKey'.
// Object literal may only specify known properties, and 'one' does not exist in type 'NumberKey'.

// This will also not work:
const pets: StringKey = {
  // Properties are strings,
  // but the values are numbers and not strings.
  dog: 1,
  cat: 2,
}
// TS error: Type 'number' is not assignable to type 'string'.
Enter fullscreen mode Exit fullscreen mode

只读索引签名

索引签名还允许您使用readonly关键字来指定只读属性。

// Create object type with index signature and readonly property:
type ReadonlyStringKey = {
  // The property will be a type of string and a readonly:
  readonly [key: string]: string | number;
}

// Create new object with shape of "ReadonlyStringKey":
const cat: ReadonlyStringKey = {
  name: 'Suzzy',
  breed: 'Abyssinian Cat',
  age: 2
}

// This will not work:
// Try to change the value of "name" property on "cat":
cat.name = 'Vicky'
// TS error: Index signature in type 'ReadonlyStringKey' only permits reading.

// Try to change the value of "age" property on "cat":
cat.age = 1
// TS error: Index signature in type 'ReadonlyStringKey' only permits reading.
Enter fullscreen mode Exit fullscreen mode

结论:TypeScript 中的对象类型简介

对象是 JavaScript 的基础组成部分。TypeScript 对象类型可以确保对象的类型安全。对象类型还能让对象操作更加便捷。希望本教程能帮助你了解 TypeScript 中的匿名对象类型和命名对象类型,以及如何使用属性修饰符和索引签名。

文章来源:https://dev.to/alexdevero/introduction-to-object-types-in-typescript-ghe