typescript - varför är `interface`-namnet samma som `var`?


Jag såg detta mönster idag i WebAssembly-paketet och blev förvirrad över denna användning.

  1. Varför är gränssnittsnamnet samma som variabelnamnet?
  2. Finns det någon skillnad mellan c1 och c2?
  3. new S({ a: 6 }), Hur ska man förstå S, är det ett gränssnitt eller variabeln?
interface S {
  a: number,
}

declare var S: {
   b: number,
   new (value?: any): S;
}

const c1 = { a: 6 };
const c2 = new S({ a: 6 });

Svar #1

Värden och typer delar inte ett namnrymd i TypeScript, så i allmänhet finns det ingen nödvändig koppling mellan ett namngivet värde (som var S) och en namngiven typ (som interface S); de råkar bara ha samma namn.

Men när du skriver en class-deklaration, skapar det både ett namngivet värde som motsvarar klasskonstruktorn och en namngiven typ som motsvarar instanser av klassen. Detta möjliggör saker som const d: Date=new Date(), där den första Date är instanstypen, och där den andra Date är klasskonstruktorns värde. (Se detta svar på en annan fråga för en lång förklaring av detta)

Så, om jag skulle råka skriva detta:

class S {
  a: number;
  static b: number = 1;
  constructor(value?: any) {
    this.a = (typeof value === "number") ? value : 123;
  }
}

Det skulle skapa ett värde som heter S som är klasskonstruktorn, och en typ som heter S som är en instans av den klassen. Klasskonstruktorn kan anropas via new S(value) där value är en valfri parameter av typen any, och den producerar ett värde av typen S. Ett värde av typen S har en egenskap a av typen number. Observera även att klasskonstruktorn S har en static egenskap som heter b av typen number.

Om du frågar "är S en variabel eller en typ" utan mer sammanhang, är svaret "det är båda" eller "det beror".

Hur som helst kan du testa dess beteende så här:

console.log(S.b) // 1
const c1: S = { a: 6 };
console.log(c1.a) // 6
console.log(c1 instanceof S) // false
const c2: S = new S({ a: 6 });
console.log(c2.a) // 123
console.log(c2 instanceof S) // true

Observera att c1 bara är ett objekt med en a-egenskap och inte nödvändigtvis är en instans av S-klassen, även om det följer S-gränssnittet. Å andra sidan är c2 definitivt en instans av S-klassen, även om dess a-egenskap inte nödvändigtvis är relaterad till värdet av a-egenskapen som skickades till konstruktorn (efter allt kan det value-argumentet vara vilket value som helst, och själva konstruktorns implementation vet inte att value har en a-egenskap och försöker inte leta efter en).


Nu, om jag skulle skriva en deklarationsfil för den klassen, skulle jag skriva detta:

declare class S {
    a: number;
    static b: number;
    constructor(value?: any);
}

Men ett annat sätt att skriva detta är att deklarera S-konstruktorn som en variabel av lämplig typ, och att deklarera ett gränssnitt med namnet S av lämplig form:

interface S {
  a: number,
}

declare var S: {
  b: number,
  new(value?: any): S;
}

Det beter sig i princip på samma sätt. Så du kan tänka på var/interface-deklarationsparet ovan som en "synonym" för class-deklarationen.


Varför skulle någon skriva det på det här sättet? Tja, det är inte helt självklart. Enligt microsoft/TypeScript#9154 tillät äldre versioner av TypeScript inte att man slår samman deklarationer med instanssidan av classer, så man lämnade det ofta som ett interface för att möjliggöra detta. Nuförtiden är det inte en fråga längre, egentligen.

I allmänhet är var/interface-paret mer flexibelt, eftersom det fortfarande finns saker du kan göra med interface-sammanfogning som du inte kan göra med class-sammanfogning. Till exempel kan du ge var en typ av ett annat interface (säg declare var S: SConstructor; interface SConstructor { /*...*/ }) och tillåta att folk senare sammanfogar nya medlemmar till konstruktorn. Och class-konstruktörer kan inte markeras som anropbara utan new, medan var kan.

Men inget av det gäller exemplet du har gett.

Lekplatslänk till kod