Typescript-为什么通用字典类型不返回可能未定义的值?

时间:2019-08-14 14:51:40

标签: typescript

使用Typescript遇到意外的事情

我一直在使用以下模式来指定对象是string个键到Foo值的字典

type Dictionary = { [id: string]: Foo }

真正奇怪的是,当我尝试使用 any 键从Dictionary中检索值时,Typescript给出的返回类型为Foo

我希望它是Foo | undefined,因为如果键未映射到任何值,当然会返回undefined

这是为什么这种行为异常的示例-see the playground here

type Foo = { bar: number }
type Dictionary = { [id: string]: Foo }

const a: Dictionary = { 
  one: { bar: 1 }, 
  two: { bar: 2 } 
}

const addOneToBar = (foo: Foo) => foo.bar + 1

// No type error, maybe expected, because we 'know' from context that 
// the one property exists on the Dictionary instance a
alert(addOneToBar(a.one))
alert(addOneToBar(a['one']))

try {
  // There is still no type error here, though. Why?
  // Shouldn't a.three / a['three'] return Foo | undefined
  // instead of Foo, because we're not certain there is any three property?
  alert(addOneToBar(a.three))
  alert(addOneToBar(a['three']))
} catch (err) {
  alert('Of course, there is a null pointer error: ' + err)
}

在我看来,这段代码显然不是类型安全的,确实会导致运行时异常,而该异常不会被Typescript标记。

我对此感到非常惊讶-返回undefined指向什么都没有的键是Javascript对象的基本行为,而且我猜想大多数语言都可以使用任何通用的字典/地图实现。我不明白为什么Typescript会错过这个。

也许我在上面的代码中犯了一个错误,或者我误解了{ [id: string]: Foo }模式的目的,并且/或者还有另一种用于在Typescript中键入字典的模式,它确实具有这种行为,请改用(我已经尝试过Record类型,再次令我惊讶的是,它也表现出相同的行为)。

这是怎么回事?


修改

我刚刚意识到写了一个问题,我可能可以type Dictionary = { [id: string]: Foo | undefined }来获得这种行为。但是,为什么这不是默认值呢?也许如果您有一个狭窄的键类型(如枚举),这是有道理的,但是如果您有一个string键,则保证始终从字典返回值是绝对错误的,不是吗?

进一步编辑

就像在评论中一样,我现在已经意识到,以这种方式行事的Typescript实际上是有道理的,并且上述解决方案明确告诉TS字典可能返回未定义的值就是解决方案。从某种意义上讲,这是我假设TS应该从类型推断事物,而不是我必须明确声明它们-您可以以任何一种方式争论这种情况-我建议它应该表现的方式对于可能会丢失键的情况是有益的,但是当您确定它们不会出现时,这很不利。 TS实际在此处的行为方式则相反。


Mods

这不是一个答案本身的问题(TypeScript只是按照设计的方式工作),但是除非得到密切的投票,否则我将把它保留下来,因为我认为这是有关该行为的有趣总结,为什么是按原样实现的。

1 个答案:

答案 0 :(得分:0)

通过声明doOnLayout,您是告诉编译器任何字符串都是此类型的有效键。然后,编译器将不假设哪些字符串是有效键,哪些不是,这实际上是在定义任何属性访问器,并且将返回type Dictionary = { [id: string]: Foo }

如果要强制只为对象定义Fooone键,则可以严格定义字典的属性:

two