[英]Multiple indexer in Indexable types in TypeScript
我正在閱讀 TypeScript 中的可索引類型。 我正在經歷這個例子。
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
// Error: indexing with a numeric string might get you a completely separate type of Animal!
interface NotOkay {
[x: number]: Animal;
// Numeric index type 'Animal' is not assignable to string index type 'Dog'.
[x: string]: Dog;
}
我無法理解為什么上面的代碼會產生問題。 我在網上瀏覽了很多文章,但無濟於事。 任何人都可以用至少一個例子用簡單的語言解釋我上面的代碼將如何產生問題,而這樣做不會。
interface Okay {
[x: string]: Dog;
[x: number]: Animal;
}
如果在此處也與 C++、Java(超類、子類)等語言相關,將不勝感激。
TL;博士:
number
類型的鍵; 它們實際上是一種特殊類型的string
鍵。number
”鍵實際上是一種特殊的string
鍵,因此任何number
索引簽名屬性都必須可分配給string
索引簽名屬性(如果存在)。 一個可能令人困惑的問題是 JavaScript 沒有真正的數字鍵。 JavaScript 中的鍵是symbol
s 或string
s... 即使對於通常被認為具有數字索引的 arrays 之類的東西也是如此。
當您使用除symbol
以外的任何類型的鍵對 object 進行索引時,如果它還不是一個string
,JS 會將其強制轉換為字符串。 因此,雖然您可以考慮在索引0
處有一個元素的單元素數組,但在技術上說它在索引"0"
處有一個元素更正確:
const arr: [string] = [""];
const arrKeys: string[] = Object.keys(arr);
console.log(arrKeys) // ["0"]
console.log(arrKeys.includes(0 as any)) // false
console.log(arrKeys.includes("0")) // true
arr[0] = "foo";
arr["0"] = "bar";
console.log(arr[0]); // "bar";
TypeScript 允許您指定鍵類型number
的索引簽名來表示類似數組的元素訪問,但它不會改變數字鍵被強制轉換為字符串的事實。 如果number
索引簽名使用像NumericString
這樣的類型會更准確。 但是在 TypeScript 中沒有暴露這種類型。 (旁白:好吧,TypeScript 4.1 的模板文字類型實際上為您提供了一種表示此類類型的方法
type NumericString = `${number}`;
但是您不能真正將其用作索引鍵類型而不是索引簽名,因此這一點有點沒有實際意義。)因此,雖然通常number
不是string
的子類型,但對於keys ,您可以將number
視為一種string
。
下一個可能的混淆點是 TypeScript 不將索引簽名視為“例外”。 如果您添加索引簽名,則它不得與任何其他屬性沖突。 (有關更多信息,請參閱此 q/a 。)例如,以下類型的baz
成員存在問題:
interface A {
foo: string; // okay
bar: number; // okay
baz: boolean; // <-- error! boolean not assignable to string | number
[k: string]: string | number;
}
索引簽名[k: string]: string | number
[k: string]: string | number
的意思是“如果你從A
中用任何string
類型的鍵讀取一個屬性,你將得到一個string | number
類型的值。” foo
屬性是兼容的,因為鍵"foo"
是string
,值類型string
可分配給string | number
string | number
。 bar
屬性是兼容的,因為鍵"bar"
是string
,並且值類型number
可分配給string | number
string | number
。 但是baz
是錯誤的。 鍵"baz"
是一個字符串,但是它違反了索引簽名; boolean
不可分配給string | number
string | number
。
您不能使用像上面這樣的索引簽名來表示“好吧,鍵"baz"
處的屬性是boolean
但所有其他string
鍵屬性的值都是string | number
類型。如果有辦法說, (有關此請求,請參閱microsoft/TypeScript#17687 ),但索引簽名不能那樣工作。
認為鍵"baz"
是string
的特例會有所幫助,因此它的屬性類型可以是string | number
的特例。 string | number
和boolean
不起作用。
所以,讓我們把它們放在一起:
interface NotOkay {
[x: number]: Animal; // error! Animal not assignable to Dog
[x: string]: Dog;
}
假設我有一個NotOkay
類型的值,我用它的一個鍵對其進行索引:
const notOkay: NotOkay = {
str: dog,
123: animal
}
const randomKey = Object.keys(notOkay)[Math.random() < 0.5 ? 0 : 1]; // string
const randomProp = notOkay[randomKey] // Dog?
console.log(randomProp.breed.toUpperCase()); // either LAB or runtime error?
這可能會導致運行時錯誤,因為鍵123
實際上是"123"
,一個string
。 NotOkay
的string
索引簽名表示string
鍵的每個屬性都將是Dog
類型。 但是等等, number
索引簽名與它不兼容。 如果我提前 go 並將每個string
索引的屬性都視為Dog
類型,我會遇到一些假設的Dog
沒有breed
的問題。
因此, number
索引簽名是一個問題。 認為鍵類型number
是string
的特例會有所幫助,因此它的屬性類型可以是Dog
的特例,而Animal
不起作用。
如果您切換Dog
和Animal
,問題就會消失:
interface Okay {
[x: string]: Animal;
[x: number]: Dog;
}
const okay: Okay = {
str: animal,
123: dog
}
const randomKey2 = Object.keys(okay)[Math.random() < 0.5 ? 0 : 1]; // string
const randomProp2 = okay[randomKey2] // Animal
console.log(randomProp2.name.toUpperCase()); // no error here, FIDO or FLUFFY
由於number
鍵是string
鍵的特例,而Dog
是Animal
的特例,所以一切正常。 您的未知string
鍵屬性已知為Animal
。 如果您將Dog
視為Animal
也沒關系,因為它是一個。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.