TypeScript: Objektet kan möjligen vara null i unionstyp
Jag har ett basgränssnitt där åtminstone en av två egenskaper är obligatorisk.
interface Base {
someProp: { subProp: string; } | null;
condProp1?: boolean;
condProp2?: boolean;
}
interface WithProp1 extends Base {
condProp1: true;
}
interface WithProp2 extends Base {
condProp2: true;
}
export type MyType = WithProp1 | WithProp2;
När jag försöker använda MyType efter att ha kontrollerat att someProp inte är null och efter att ha kontrollerat antingen condProp1 eller condProp2 för sanning, tror kompilatorn fortfarande att someProp kan vara null.
function doSomething(myType: MyType) {
if (myType.someProp && (
myType.condProp1 && myType.someProp.subProp === 'foo' ||
myType.condProp2 && myType.someProp.subProp === 'bar')) {
console.log("It's true, isn't it? It's really true.");
}
}
I doSomething() ger myType.someProp.subProp ett kompileringsfel som säger att someProp kan vara null. Detta händer inte om jag ersätter MyType med Base i funktionssignaturen.
// Compiles fine
function doSomethingBase(myType: Base) {
if (myType.someProp && (
myType.condProp1 && myType.someProp.subProp === 'foo' ||
myType.condProp2 && myType.someProp.subProp === 'bar')) {
console.log("It's true, isn't it? It's really true.");
}
}
Att byta ordning till myType.someProp.subProp === 'foo' && myType.condProp1 fungerar som en lösning för den första raden, men inte den andra. Någon idé om varför detta händer eller hur man kan kringgå det (jag kan använda en ! men jag borde inte behöva det)? Verkar som ett fel i kompilatorn.
Jag använder TSC version 3.5.3. TypeScript-lekplats
Svar #1
Jag misstänker att detta faktiskt är en kompilatorbugg; jag känner inte till internals tillräckligt väl för att säga säkert, men jag misstänker att kompilatorn utvärderar det initiala skyddsklausulen som typ Base, och sedan de inre klausulerna som WithPropX, och förlorar informationen om att myType som WithPropX redan har skyddats mot att someProp är null.
Du kan kringgå detta genom att invertera ordningen på kontrollerna, så att den mer specifika typinferensen sker tidigare i villkoret (och då sker kontrollen av someProp null vid den punkt där kompilatorn vet att myType är en WithPropX, snarare än en Base):
function doSomething(myType: MyType) {
if (myType.condProp1 && myType.someProp && myType.someProp.subProp === 'foo' ||
myType.condProp2 && myType.someProp && myType.someProp.subProp === 'bar') {
console.log("It's true, isn't it? It's really true.");
}
}
Detta problem verkar som att det kan vara relaterat till det du observerar här.
Svar #2
Jag gillar vad @Chris Helad sa:
Jag misstänker att kompilatorn bedömer det initiala skyddsklausulen som typ Base, och sedan de inre klausulerna som WithPropX, och förlorar informationen att myType som WithPropX redan har skyddats mot en null someProp.
Så, kompilatorn fungerar förmodligen så här:
function doSomething(myType: MyType) {
if (myType.someProp && ( // myType is type of WithProp1 | WithProp2 AND someProp is defined
myType.condProp1 && myType.someProp.subProp === 'foo' || // condProp1 is defined, so, myType is typeof WithProp1 (forgot that someProp is defined)
myType.condProp2 && myType.someProp.subProp === 'bar')) { // condProp2 is defined, so, myType is typeof WithProp2 (forgot that someProp is defined)
console.log(`It's true, isn't it? It's really true.`);
}
}
För att övervinna det kan du definiera en enkel typvakt för att berätta för kompilatorn om den faktiska typen:
function hasProp(t: MyType): t is MyType & {someProp: NonNullable<MyType['someProp']>} {
return !!t.someProp;
}
function doSomething(myType: MyType) {
if (hasProp(myType) && (
myType.condProp1 && myType.someProp.subProp === 'foo' ||
myType.condProp2 && myType.someProp.subProp === 'bar')) {
console.log(`It's true, isn't it? It's really true.`);
}
}
