Hur man löser att TypeScript är för restriktivt med Array.includes()


Jag borde få tillåtas passera vad som helst till Array.includes för att kontrollera om det finns i arrayen, men TypeScript vill inte att jag skickar in något som inte är rätt typ. Till exempel:

type Fruit = "apple" | "orange";
type Food = Fruit | "potato";

const fruits: Fruit[] = ["apple", "orange"];

function isFruit(thing: Food) {
  return fruits.includes(thing); // ts error: "potato" is not assignable to type 'Fruit'.
}

Lekplats

Vad är ett rent sätt att fixa denna kod med minimal påverkan på läsbarheten?

Svar #1

Först, vänligen läs denna QA från TypeScript 3.x-dagarna där någon i princip ställde samma fråga som du: TypeScript const-assertioner: hur man använder Array.prototype.includes?

Nu, i ditt fall, som ett alternativ till @CertainPerformance:s förslag om unknown[] (vilket förlorar typinformation), kan du lagligt bredda fruits till readonly string[] (utan att använda as), vilket är kompatibelt med Fruit och Food:

type Fruit = "apple" | "orange";
type Food = Fruit | "potato" | "egg";

const fruits: readonly Fruit[] = ["apple", "orange"];

function isFruit(food: Food): food is Fruit {

  const fruitsAsStrings: readonly string[] = fruits;
  return fruitsAsStrings.includes(food);
}

Ett alternativ, (teoretiskt mer "korrekt") tillvägagångssätt är att lägga till ett variant includes-medlem i ReadonlyArray<T>-gränssnittet (som föreslaget i den länkade QA:n) som tillåter att U är en supertyp av T istället för tvärtom.

interface ReadonlyArray<T> {
  includes<U>(x: U & ((T & U) extends never ? never : unknown)): boolean;
}

type Fruit = "apple" | "orange";
type Food = Fruit | "potato" | "egg";

const fruits: readonly Fruit[] = ["apple", "orange"];

function isFruit(food: Food): food is Fruit {

  return fruits.includes(food);
}


Med det sagt... om du avser att använda en kollektionstyp som ett värde-/typmedlemskapstest, bör du använda en JavaScript Set<T> (eller använda object-nycklar) istället för en Array: inte bara av prestandaskäl (eftersom både Set<T>.has() och object-nyckeluppslagning är O(1) medan Array.includes() är O(n)), utan också för att TypeScript fungerar bättre med keyof-typer.

...att implementera det är en övning för läsaren.

Relaterade artiklar