2024年5月9日发(作者:)

a record is preferred over an

index signature

在 TypeScript 中,有两种主要的方式用于描述对象

类型:属性和索引签名。属性是一个直接描述对象属性的

方式,它定义了对象的属性名和属性值类型。而索引签名

则是一种使用“键”来访问并描述对象属性的方式。在使

用 TypeScript 进行类型检查时,我们可以使用这两种方

式来描述对象类型。然而,在实际应用中,一个记录类型

(Record Type)往往比索引签名更为优秀。

何为记录类型?

记录类型是 TypeScript 中的一种类型定义方法,它

定义了一个记录类型,可以被用来将类似的可枚举属性聚

合成一个类型,并为属性提供具体的类型。例如:

``` type SomeType = Record; ```

这种方式能够将一个对象的属性名和值的类型定义的

更加明确。这里的 `Record` 表示的是一种提供类型定义

的工具函数,其定义了一个由字符串类型作为键的对象,

并且每一个键所对应的值的类型都必须是数字类型。换句

话说,这个定义了一个拥有任意键但值全部为数字类型的

对象。

为什么记录类型比索引签名优秀?

索引签名比记录类型更加灵活,因为索引签名能够描

述任意类型的属性访问。这就使得索引签名在某些场合下

更加适用。但是,对于非常规的属性访问类型,会给

TypeScript 带来许多困惑和错误提示。

举个例子:

``` interface Foo { [prop: string]: string;

bar: number; }

let x: Foo = { bar: 1 }; let y: string = ;

```

上述代码中定义了一个接口 `Foo`,它包含了一个索

引签名和一个名为 `bar` 的属性,属性的类型分别为

`string` 和 `number`。接下来,我们定义了一个变量

`x`,类型为 `Foo`,它仅包含 `bar` 属性。最后,我们

定义了一个字符串类型变量 `y`,并令它等于变量 `x` 中

一个名为 `qux` 的属性值,这个属性实际上是不存在的。

在这个例子中,由于 `x` 中没有名为 `qux` 的属

性,因此 TypeScript 会提示以下错误:

``` Property 'qux' does not exist on type

'Foo'. Did you mean to access the 'bar' property

instead? ```

这种错误提示显然是非常有帮助的。然而,如果我们

修改接口 `Foo`,并将索引签名改为记录类型:

``` interface Foo { [prop: string]: string;

bar: number; }

let x: Record & { bar: number }

= { bar: 1 }; let y: string = ; ```

这时,我们发现无论我们向变量 `y` 中访问哪个不存

在的属性名,TypeScript 都不会给出任何错误提示。这显

然是一个不好的设计,因为一些类型错误可能会不被及时

发现并解决。

因此,在大多数情况下,我们应当优先使用记录类

型,以更好地减少类型错误的发生。

疑问:那既然记录类型定义时限制的是字符串类型作

为键,我们在定义时就没有更好的表达自己的设计思路?

回答:在一些特定的场合下,我们可以向 `Record`

函数中传入其它类型参数以定制更加符合需要的记录类

型。例如:

``` type Resource = "users" | "roles" |

"permissions" type ACL = Record;

```

在这个例子中,我们将字符串类型的联合类型作为

`Resource` 类型的定义。然后,我们就可以使用这个类型

来定义一个名为 `ACL` 的记录类型,并将其作为访问控制

列表的键值类型。在这里,我们利用了字符串类型的联合

类型,更好地表述了应用场景中的业务含义。

总结

记录类型是 TypeScript 中用来定义可枚举属性的一

种优秀方式。与索引签名不同的地方在于,记录类型定义

了一个具体的属性名,从而增强了代码的类型安全性。在

使用 TypeScript 进行开发时,我们应当优先使用记录类

型,以更好地减少类型错误的发生。