typeguards - Typkontroll baserat på annan variabel i TypeScript
Är det möjligt i TypeScript att ha en variabel som indikerar typen på andra variabler? I slutändan skulle jag vilja bara kontrollera en variabel stateIndicator som är beroende av andra variabler för att anta deras typer.
type A = {prop1: string}
type B = {prop2: number}
function isA(x: A | B): x is A {
return (x as A).prop1 !== undefined
}
function foo1(data: A | B) {
const stateIndicator = isA(data);
if (stateIndicator) {
// this is fine
const myString: string = data.prop1;
}
}
function foo2(data: A | B) {
const stateIndicator = isA(data) ? "stateA" : "stateB";
if (stateIndicator === "stateA") {
// here unless I check if isA(data) or do "data as A" the data.prop1 shows error
// Property 'prop1' does not exist on type 'B'
const myString: string = data.prop1;
}
}
Svar #1
Nej, det du letar efter är inte möjligt i TypeScript. Se microsoft/TypeScript#46915 för ett kanoniskt svar.
I allmänhet kan TypeScript-kompilatorn inte spåra alla möjliga logiska konsekvenser av din kod. Det vill säga, den gör inte kontrafaktiskt resonerande som "i kroppen av foo2(), om stateIndicator är "stateA" måste det betyda att isA(data) returnerade true, eftersom om det hade returnerat false skulle stateIndicator vara "stateB" vilket inte är lika med "stateA" och det finns inget annat sätt som stateIndicator kan vara "stateA". Så därför måste data vara en A". Det skulle vara för kostsamt att göra något sådant i allmänhet.
I de flesta fall kommer alla kontroller du utför på en variabel (som stateIndicator) högst påverka den uppenbara typen av just den variabeln, och inte andra variabler vars historik kan vara sammanflätad med den. Så stateIndicator === "stateA" begränsar stateIndicator själv, men inget annat. Även kontroll av en egenskap hos ett objekt påverkar generellt endast den uppenbara typen av den egenskapen, och inte den uppenbara typen hos moderobjektet, om inte moderobjektet är av en diskriminerad unionstyp och den kontrollerade egenskapen är dess diskriminant.
Det finns några fall där något sådant här faktiskt händer. TypeScript 4.4 introducerade styrflödesanalys av aliaserade villkor och diskriminanter. Det betyder att om du tilldelar boolean-resultatet av en ja-nej-typkontroll till en const, eller tilldelar diskriminant-egenskapen hos en diskriminerad union till en const, så kommer efterföljande kontroller av den const att begränsa typen av den relaterade variabeln. Detta är vad som gör att din foo1-implementation fungerar; stateIndicator är ett aliaserat typkontroll-boolean. Men i foo2 är stateIndicator varken ett alias av en boolean-kontroll eller ett alias av en diskriminant-egenskap hos en diskriminerad union. Så ingen begränsning sker.
Eftersom det du försöker göra inte är möjligt kommer du att behöva hitta en lösning runt det. Generellt sett är det renaste och mest välstödda tillvägagångssättet att göra unionen till en diskriminerad union genom att lägga till en diskriminant-egenskap till varje medlem, och sedan kontrollera diskriminanten:
type A = { stateIndicator: "stateA", prop1: string }
type B = { stateIndicator: "stateB", prop2: number }
function foo(data: A | B) {
if (data.stateIndicator === "stateA") {
const myString: string = data.prop1;
} else {
const myNumber: number = data.prop2;
}
}
Men uppenbarligen kommer dina användningsfall att styra valet av lösning.
Lekplatslänk till kod
