typescript - TS härleder typen "never" eftersom det inte kan förstå tilldelning i en forEach-loop


Felet lyder Property 'id' does not exist on type 'never'.

Jag förstår tillräckligt om TypeScript för att ha trott att Control Flow Analysis skulle täcka mig i detta scenario. När jag deklarerar:

let quantityRange: QuantityLevels | false = false;

Och, medan man itererar genom en av funktionsparametrarna, kan en QuantityLevels-typad variabel tilldelas:

priceList.quantityLevels.forEach((ql) => {
  if (ql.min <= quantity && ql.max >= quantity) {
    quantityRange = ql;
  }
});

När vi eliminerar möjligheten att false är värdet som tilldelas quantityRange för resten av funktionen genom att kasta ett fel i det fallet. Däremot härleder TypeScript att variabeln quantityRange inte kan ha tilldelats något annat än false, och är typad som never för resten av funktionen.

När jag försöker använda egenskaperna hos quantityRange blir jag avvisad:

const priceByProduct = priceList.prices[product.id][quantityRange.id].price;
// error: Property 'id' does not exist on type 'never'.

Vad händer här? Om jag gör en uppgift av

quantityRange = priceList.quantityLevels[0];

(priceList.quantityLevels som skrivs som Array<QuantityLevels>), då är allt väl. Detta får mig att tro att jag måste ha en ofullständig förståelse för hur TypeScript hanterar funktions-/block-skopade variabler. Jag har läst igenom det jag tror är de relevanta delarna av TypeScript-handboken och har gått lottlös (eller kanske tom i huvudet).

Koden klarar inte en build-åtgärd, men i utvecklings- och testmiljöerna fungerar den som förväntat.

Koden nedan återges också i TypeScript-lekplatsen, tillsammans med fel.

function getPrice({ variantID, quantity, priceList }: GetPriceProps) {
  let quantityRange: QuantityLevels | false = false;

  priceList.quantityLevels.forEach((ql) => {
    if (ql.min <= quantity && ql.max >= quantity) {
      quantityRange = ql;
    }
  });

  // quantityRange = priceList.quantityLevels[0];
  // ^ Manually assigning this (which is what's happening in the above for loop) 
  //   makes TS understand.

  if (!quantityRange) {
    throw new Error(`No quantity range found for ${quantity} of ${variantID}.`);
  }

  const product = priceList.targets.find(
    (t) => t.variants.find((v) => v.id === variantID),
  );

  if (!product) {
    throw new Error(`Could not find a product related to variant ${variantID}.`);
  }

  const priceByProduct = priceList.prices[product.id][quantityRange.id].price;
  const priceByVariant = priceList.prices[product.id][quantityRange.id][variantID];

  return (priceByVariant || priceByProduct);
};

type QuantityLevels = {
  id: string;
  min: number;
  max: number;
}

interface NamedEntity {
  id: string;
  name: string; 
};

interface Product extends NamedEntity {
  variants: Array<NamedEntity>;
}

type PriceList = {
  targets: Array<Product>;
  quantityLevels: Array<QuantityLevels>;
  prices: {
    [productID: string]: {
      [quantityLevelID: string]: {
        price: number;
        [variantID: string]: number;
      }
    }
  }
};

type GetPriceProps = {
  variantID: string;
  quantity: number;
  priceList: PriceList;
};

Svar #1

Att använda forEach och argument-closure gör TypeScript mindre exakt med sin kontrollflödesanalys. Denna TypeScript-fråga innehåller lite mer information.

Om du ersätter

priceList.quantityLevels.forEach((ql) => {
  if (ql.min <= quantity && ql.max >= quantity) {
    quantityRange = ql;
  }
});

med en for-of-loop:

for (const ql of priceList.quantityLevels) {
  if (ql.min <= quantity && ql.max >= quantity) {
    quantityRange = ql;
  }
}

Typen av quantityRange härleds korrekt.

TypeScript-lekplats

Svar #2

Använd en typförklaring när du instansierar variabeln istället för en annotation. Detta kommer att förhindra att TS härleder att den endast är false därefter i resten av scopet, även i kodblock som innebär svårigheter med kontrollflödesanalys.

let quantityRange = false as QuantityLevels | false;

Din modifierade kod i TS Playground

Relaterade artiklar