簡體   English   中英

TypeScript 中可轉位類型中的多個分度器

[英]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;博士:

  • JS/TS 中的對象實際上並沒有number類型的鍵; 它們實際上是一種特殊類型的string鍵。
  • TS 中的索引簽名不能與其他索引簽名或屬性沖突。
  • 由於“ 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 | numberboolean不起作用。


所以,讓我們把它們放在一起:

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 NotOkaystring索引簽名表示string鍵的每個屬性都將是Dog類型。 但是等等, number索引簽名與它不兼容。 如果我提前 go 並將每個string索引的屬性都視為Dog類型,我會遇到一些假設的Dog沒有breed的問題。

因此, number索引簽名是一個問題。 認為鍵類型numberstring的特例會有所幫助,因此它的屬性類型可以是Dog的特例,而Animal不起作用。


如果您切換DogAnimal ,問題就會消失:

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鍵的特例,而DogAnimal的特例,所以一切正常。 您的未知string鍵屬性已知為Animal 如果您將Dog視為Animal也沒關系,因為它是一個。


Playground 代碼鏈接

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM