Typescript - 為什麼泛型字典類型不返回可能未定義的值?
在 Typescript 中碰到了一些非常意外的事情
我一直使用以下模式來指定一個物件是 string 鍵對應 Foo 值的字典
type Dictionary = { [id: string]: Foo }真正奇怪的是,當我嘗試使用任何鍵從字典中檢索值時,Typescript 給出的返回類型是Foo
我會期望它是 Foo | undefined,因為當然如果鍵沒有對應到任何值,則會返回 undefined。
這是一個說明為什麼這會出現意外行為的例子——在這裡查看試驗場
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 } 來實現這個行為。但話說回來,為什麼這不會是預設行為呢?也許如果你的鍵類型很窄,比如 enum,那會說得通,但如果你有一個 string 鍵,保證 Dictionary 總是會返回一個值顯然是不對的,對吧?
進一步編輯
正如評論中所說,我現在已經意識到 Typescript 這樣的行為其實是有道理的,而上面的方法,即明確地告訴 TS 該字典可能返回 undefined 值,才是解決方案。在某種意義上,這相當於我假設 TS 應該從類型中推斷,而不是我必須明確指出——你可以從便利性的角度對這點有不同看法——我提出的方法在鍵可能缺失的情況下表現良好,但在你確定鍵不會缺失的情況下表現會較差。而對於 TS 實際的行為則正好相反。
模組
這本身不是一個有明確答案的問題(TypeScript 正在按設計運作),但我打算保留它,除非它獲得關閉票,因為我認為這是對其行為以及為何如此實現的一個有趣總結。
答案#1
透過宣告 type Dictionary={ [id: string]: Foo },你是在告訴編譯器任何字串都是此類型的有效鍵。編譯器將不會對哪些字串是有效鍵、哪些不是做出假設,本質上是說任何屬性存取都是已定義的,並且會回傳一個 Foo。
如果你想強制只定義 one 和 two 鍵於你的對象,則可以嚴格定義字典的屬性:
type Dictionary = {
one: Foo;
two: Foo;
}
