typescript 索引签名键限制

gjmwrych  于 2023-01-14  发布在  TypeScript
关注(0)|答案(3)|浏览(158)

我正在尝试一些TS索引签名示例,感觉键的限制行为非常不一致。

const s = Symbol();

type DictNumber = {
  [key: number]: string;
}

const dictNumber: DictNumber ={
  1: 'andy', // no error, which is what I defined.
  'foo': 'bar', // Error, because the key is string not a number.
  [s]: 'baz', // no error, but why ??? [s] is a symbol not a number right?
}

type DictString = {
  [key: string]: string;
}

const dictString: DictString={
  1: 'andy', // no error? why? is it converted from number to string for me?
  'foo': 'bar', // no error, which is what I defined.
  [s]: 'baz', // no error, but why ??? [s] is a symbol not a string right?
}

type DictSymbol= {
  [key: symbol]: string;
}

const dictSymbol: DictSymbol={
  1: 'andy', // Error, because the key is number not a symbol.
  'foo': 'bar', // no error, why?
  [s]: 'baz', // no error,  which is what I defined.

我有noImplicitAnyalwaysStrict以及.这里是操场
我可能错过了一些非常基本的东西,有人能给我解释一下为什么会发生这种情况吗?

cgh8pdjw

cgh8pdjw1#

它比表面上看起来更有趣。考虑一下这个:

const s = Symbol();

type DictNumber = {
  [key: number]: string;
}

const dictNumber: DictNumber = {
  1: 'andy', // ok
  'foo': 'bar', // error
  [s]: 'baz', // ok
}

但尝试修复foo密钥:

const dictNumber: DictNumber = {
  1: 'andy', // ok
  '2': 'bar', // ok
  [s]: 'baz', // error
}

所以,现在我们知道,有一个错误,但由于错误突出显示算法,它没有突出显示,因为有另一个错误,这是抛出更快。或者尝试只是替换它们:

const dictNumber: DictNumber = {
  1: 'andy', // ok
  [s]: 'baz', // error
  'foo': 'bar', // ok
}

我理解TS团队这样做的原因,为了性能。如果我们已经有了错误,就不需要再验证更多的键了。
const dictSymbol: DictSymbol也是一样。它工作正常,试着更换它们。
只有DictString没有达到我们的预期,完全没有错误。

const s = Symbol();

type DictSymbol = Record<symbol, string>

type DictString = Record<string, string>

let dictString: DictString = {
  a: 'baz',
}

let dictSymbol: DictString = {
  [s]: 'baz', // no error , but should be
}

dictString = dictSymbol // ok
dictSymbol = dictString // ok

根据我的经验,这是因为Record<K,V>没有interface表示安全,因为类型别名是默认索引的,请看我的答案。
为了安全起见,请使用interface

const s = Symbol();

interface DictSymbol {
  [sym: symbol]: string
}

type DictString = Record<string, string>

let dictString: DictSymbol = {
  a: 'baz', // error
}

let dictSymbol: DictString = {
  [s]: 'baz', // no error , but should be
}

dictString = dictSymbol // ok
dictSymbol = dictString // error

如果您将DictString转换为interface,则会出现更多错误:

const s = Symbol();

interface DictSymbol {
  [sym: symbol]: string
}

interface DictString {
  [str: string]: string
}

let dictString: DictSymbol = {
  a: 'baz', // error
}

let dictSymbol: DictString = {
  [s]: 'baz', // still no error
}

dictString = dictSymbol // ok
dictSymbol = dictString // error

我不知道为什么这里没有错误。我希望

let dictSymbol: DictString = {
  [s]: 'baz', // still no error
}
gtlvzcf8

gtlvzcf82#

这里有许多问题在起作用。
具有如下索引签名

type DictNumber = {
  [key: number]: string;
}

意味着每个 numeric 属性必须具有string类型。但是 non-numeric 属性的类型没有任何限制。这意味着这是可以的:

const a = {
  0: "123",
  abc: 123, // non-numeric property of number type
  [s]: 123  // symbol property of number type
}

const b: DictNumber = a // Ok

编辑:正如来自乌克兰的@captain-yossarian在他的回答中指出的,存在一个多余的属性检查错误。至少在数字索引签名方面是这样。

您预期的错误主要取决于 * 多余的属性检查 *。是的,当您将 * 对象文本 * 赋给具有显式类型的变量时,TypeScript确实执行多余的属性检查。

const dictNumber: DictNumber = {
  1: 'andy', 
  'foo': 'bar', // Error, excess property check
}

但是目前存在#44794中描述的一个bug。当索引签名存在时,编译器不会在过多的属性检查中检测symbol-属性。

const dictString: DictString = {
  1: 'andy',
  [s]: 'baz', // No Error, but there should be
}

这是一种意外行为,可能会在将来某个时候修复。
最后:

const dictString: DictString = {
  1: 'andy'
}

当索引时数字被强制为字符串,这就是为什么当string索引签名存在时允许数字作为属性的原因。

pw9qyyiw

pw9qyyiw3#

TL;DR

JS对象不是字典,您要查找的是Map

长答案

符号保证是唯一的,因此不是普通的键,它们作为一种存储对象的半私有(可以用Object.getOwnPropertySymbols()访问)属性的方式存在,而不会与其他键冲突。这是一种弱封装形式。
TypeScript知道符号不会干扰其他对象键,因此允许将它们用作键。
由于JavaScript对象不是字典,就像Python和c#中的字典一样,你可以在同一个对象中混合特殊键。TypeScript是JavaScript的超集,必须支持这种行为,你可以向任何对象添加私有属性,而不管键的类型。
此外,由于对象已经具有本机Symbol键,因此可以添加更多的Symbol键。
至于这种行为的合理性,JS的答案通常是“因为它很奇怪”。
使用“record”代替“dict/dictionary”更好,它也是一个built in utility type in TypeScript

相关问题