typescript - TS2339 - 'Egenskapen finns inte på typen' fel i en till synes giltig TS-fil


Jag har följande typdeklarationer:

type Root = { r: string };
type A = { a: string };
type B = { b: string };
type Main = Root & (A | B);

Eftersom Main är ekvivalent med {r: string, a: string} | {r: string, b: string}, fungerar detta:

const main: Main = {
    r: 'r',
    a: 'a'
}

Inga överraskningar här. Dock ger detta följande fel:

const func : (main: Main) => void = main => {
    main.a
    /*   ^
    * [TS2339]
    * Property 'a' doesn't exist on type 'Main'.
    *   Property 'a' doesn't exist on type 'Root & B' */
}

Jag förstår att .a inte finns på Root & B, men det finns på Root & A, så det måste finnas på (Root & A) | (Root & B) vilket är ekvivalent med Main, eller hur?

Är det ett fel, eller missar jag något?

Svar #1

Du har rätt om Main är ekvivalent med (Root & A) | (Root & B), men du har fel om tolkningen av unionstyper (|). En unionstyp betyder att du kan tilldela vilken typ som helst i unionen till main (så antingen (Root & A) eller (Root & B)), men du kan bara komma åt gemensamma egenskaper hos unionens medlemmar. I det här fallet är den gemensamma medlemmen endast r. Eftersom main kan vara antingen (Root & A) eller (Root & B), kanske a inte finns på main, så typescript förhindrar sådan åtkomst.

För att använda de medlemmar som endast finns på en typ i unionen behöver du en typvakt. I det här fallet fungerar en in-typvakt bäst:

const func : (main: Main) => void = main => {
    if ('a' in main) {
        main.a
    } else {
        main.b
    }
}

Du kan läsa mer om typvakter här

Svar #2

Det finns problemet att kompilatorn inte kan vara säker på att antingen .a eller .b är tillgängliga vid denna punkt, eftersom det kommer att valideras vid körning. Så snart du skapar en variabel av typen Main, kommer du att specificera vilken specialiserad typ av Main det är. Så antingen Root & A eller Root & B.

För att säkerställa att .a eller .b är åtkomliga inom den funktionen, gör bara följande:

type Root = { r: string };
type AB = { a?: string, b?: string };
type Main = Root & AB;

const func = (main: Main): void => {
    if ('a' in main) {
        main.a!
    } else {
        main.b!
    }
}

Relaterade artiklar