typescript - Denna jämförelse verkar vara oavsiktlig eftersom typerna 'this' och 'Derived' inte har någon överlappning


Jag har en klass Base och en klass Derived som ärvts från den. Base har en metod som tar en Derived som parameter och jämför this med den:

class Base {
    foo(bar: Derived): void {
        if(this === bar){
            return;
        }
        //Do stuff
    }
}

class Derived extends Base {
    baz(): void {}
}

Lekplatslänk

När jag försöker kompilera det får jag detta fel:

error TS2367: This comparison appears to be unintentional because the types
 'this' and 'Derived' have no overlap.

3         if(this === bar){
             ~~~~~~~~~~~~

Jag förstår inte varför jag får detta fel, this och Derived är båda av typen Base så de har en överlappning. Om jag skulle göra const x=new Derived(); x.foo(x); skulle det gå in i if-blocket, så jämförelsen är inte meningslös som kompilatorn verkar tro.

Intressant nog, om jag tar bort baz()-metoden så att Derived har en tom kropp, försvinner felet (men det löser inte mitt problem eftersom det inte är mycket poäng med att ha en klass med en tom kropp).

Varför får jag detta fel och hur åtgärdar jag det?

Svar #1

Formuleringen av felmeddelandet är förmodligen vilseledande. Det är rimligt att tolka "ingen överlappning" som att det är omöjligt att ha ett värde som både är av typen this och av typen Derived, vilket uppenbarligen inte är sant. Felmeddelandet brukade vara ännu värre och säga "denna jämförelse är alltid false" istället för "detta verkar vara oavsiktligt". Det ändrades, se microsoft/TypeScript#27910, men förvirringen kring "ingen överlappning" kvarstår, och folk har rapporterat problem om det som microsoft/TypeScript#60583 och microsoft/TypeScript#44645 och microsoft/TypeScript#37155. Några av dessa klassificeras som buggar, men generellt när en förklaring ges av TS-teamet är det att existensen av ett fel inte är en bugg, men meddelandet skulle kunna förbättras.

Vad som faktiskt händer är att TypeScript har olika heuristiker för att avgöra om användningen av en jämförelseoperator som === sannolikt är avsiktlig eller osannolikt avsiktlig. I grova drag är regeln: expr1 === expr2 är tillåtet om typen av expr1 är kompatibel med typen av expr2, eller tvärtom. "Kompatibel med" här betyder något i stil med "tilldelningsbar till" eller "en underordnad typ av" eller "en inskränkning av". Detaljerna är inte superviktiga.

Och i ditt fall kan du inte tilldela this till ett värde av typen Derived, och du kan inte heller tilldela bar till ett värde av typen this, så ingen av dem kan tilldelas till den andra. Så TypeScript ser detta som en indikation på att din jämförelse kanske är oavsiktlig. Det finns liknande situationer där utvecklare skulle vara glada över att få det felmeddelandet eftersom de faktiskt hade skrivit fel sak. Som nämnts i en kommentar i microsoft/TypeScript#44645:

Det här är de typer av kontroller där vi när vi stramar åt dem ens litegrann, hittar stora mängder faktiska buggar i riktig kod.

Det finns alla slags misstag du kan göra med === som inte är bevisligen alltid falska men mycket osannolika att vara avsiktliga. Att ge ett litet antal falska positiva i märklig kod för att få sanna positiva i dålig kod är den kompromiss som alla typkontrollerare gör med flit.

I situationer där TypeScript markerar din jämförelse som sannolikt oavsiktlig men du verkligen menar det, kan du vidga ena sidan av jämförelsen till en supertyp av den andra. Som du sa är this och Derived båda av typen Base. Så vidga någon av sidorna till Base och felet försvinner:

if (this as Base === bar) { }
if (this === bar as Base) { }

Det enklaste tillvägagångssättet är att använda en typförsäkran för att bredda, men eftersom typförsäkran också kan osäkert smalna, kanske du föredrar att undvika det eller komplettera det med något säkrare:

const thisBase: Base = this; if (thisBase === bar) { }
if (this satisfies Base as Base === bar) { }

Oavsett vad måste du dock hoppa igenom en liten loop för att övertyga TypeScript om att du gör detta medvetet. Det är inte optimalt, men för varje "åh kom igen, detta är uppenbarligen avsiktligt" visar det sig finnas ganska många "åh, detta var faktiskt ett misstag". Den här typen av saker är alltid heuristiska till sin natur, och eftersom utvecklarens avsikt inte kan fångas perfekt, är det bästa de kan göra att försöka hitta den punkt där kompromissen maximerar genomsnittlig utvecklarupplevelse.

Lekplatslänk till kod