typescript - Cum funcționează interfețele cu semnături de construcție?


Am câteva dificultăți în a înțelege cum funcționează definirea constructorilor în interfețe. S-ar putea să înțeleg complet greșit ceva. Dar am căutat răspunsuri destul de mult timp și nu am putut găsi nimic legat de acest lucru.

Cum implementez următoarea interfață într-o clasă TypeScript:

interface MyInterface {
    new ( ... ) : MyInterface;
}

Anders Hejlsberg creează o interfață care conține ceva similar cu asta în acest videoclip (în jurul minutului 14). Dar pentru viața mea nu pot să implementez asta într-o clasă.

Probabil că înțeleg greșit ceva, ce nu înțeleg?

EDITEAZĂ :

Ca să clarific. Prin „nou (... )” am vrut să spun „orice”. Problema mea este că nu reușesc să fac să funcționeze nici măcar cea mai simplă versiune a acestuia:

interface MyInterface {
    new () : MyInterface;
}

class test implements MyInterface {
    constructor () { }
}

Acesta nu se compilează pentru mine; primesc „Clasa 'test' declară interfața 'MyInterface' dar nu o implementează: Tipul 'MyInterface' necesită o semnătură de constructor, dar tipul 'test' nu are una” atunci când încerc să o compilez.

EDITEAZĂ :

Așa că, după ce am cercetat puțin mai mult acest lucru, având în vedere feedback-ul.

interface MyInterface {
    new () : MyInterface;
}

class test implements MyInterface {
    constructor () => test { return this; }
}

Nu este TypeScript valid și aceasta nu rezolvă problema. Nu poți defini tipul de returnare al constructorului. Va returna „test”. Semnătura următoare: class test { constructor() { } } Pare să fie „new() => test” (obținută prin plasarea cursorului peste „class” în editorul online cu doar acest cod lipit). Și acesta este ceea ce ne-am dori și ceea ce credeam că va fi.

Poate cineva să ofere un exemplu de acest lucru sau ceva similar unde se compilează efectiv?

EDITARE (din nou...):

Așadar, s-ar putea să fi venit cu o idee despre motivul pentru care este posibil să definești asta într-o interfață, dar nu este posibil să implementezi într-o clasă TypeScript. Următorul funcționează:

var MyClass = (function () {
    function MyClass() { }
    return MyClass;
})();

interface MyInterface {
    new () : MyInterface;
}

var testFunction = (foo: MyInterface) : void =>  { }
var bar = new MyClass();
testFunction(bar);

Deci, este aceasta doar o caracteristică a TypeScript care îți permite să faci interfață cu JavaScript? Sau este posibil să o implementezi în TypeScript fără a fi nevoie să implementezi clasa folosind JavaScript?

Răspuns #1

Semnăturile de tip construct în interfețe nu pot fi implementate în clase; ele sunt doar pentru definirea API-urilor JS existente care definesc o funcție ce poate fi utilizată cu 'new'. Iată un exemplu care implică semnături new în interfețe care funcționează:

interface ComesFromString {
    name: string;
}

interface StringConstructable {
    new(n: string): ComesFromString;
}

class MadeFromString implements ComesFromString {
    constructor (public name: string) {
        console.log('ctor invoked');
    }
}

function makeObj(n: StringConstructable) {
    return new n('hello!');
}

console.log(makeObj(MadeFromString).name);

Acest lucru creează o constrângere reală pentru ceea ce poți invoca cu makeObj:

class Other implements ComesFromString {
    constructor (public name: string, count: number) {
    }
}

makeObj(Other); // Error! Other's constructor doesn't match StringConstructable

Răspunsul #2

În căutarea mea pentru exact aceeași întrebare, am căutat cum a făcut echipa TypeScript asta...

Ei declară o interfață și ulterior o variabilă cu un nume care corespunde exact numelui interfeței. Aceasta este, de asemenea, modul de a tipiza funcțiile statice.

Exemplu din lib.d.ts:

interface Object {
    toString(): string;
    toLocaleString(): string;
    // ... rest ...
}
declare var Object: {
    new (value?: any): Object;
    (): any;
    (value: any): any;
    // ... rest ...
}

Am încercat asta și funcționează de minune.

Răspunsul #3

Ei bine, o interfață cu o semnătură de constructor nu este menită să fie implementată de nicio clasă (la prima vedere aceasta poate părea ciudat pentru cei cu experiență în C#/Java ca mine, dar acordă-i o șansă). Este puțin diferită.

Pentru un moment, gândește-te la asta ca la o interfață cu o semnătură de apel (ca un @FunctionalInterface în lumea Java). Scopul său este de a descrie un tip de funcție... într-un fel. Semnătura descrisă ar trebui să fie satisfăcută de un obiect funcție... dar nu orice funcție de nivel înalt sau metodă. Ar trebui să fie o funcție care știe cum să construiască un obiect, o funcție care este apelată atunci când este utilizat cuvântul cheie new.

Deci, o interfață cu o semnătură de constructor definește semnătura unui constructor! Constructorul clasei tale ar trebui să respecte semnătura definită în interfață (gândește-te la asta ca și cum constructorul implementează interfața). Este ca o fabrică!

Iată un scurt fragment de cod care încearcă să demonstreze cea mai comună utilizare:

interface ClassicInterface { // old school interface like in C#/Java
    method1();
    ...
    methodN();
}

interface Factory { //knows how to construct an object
    // NOTE: pay attention to the return type
    new (myNumberParam: number, myStringParam: string): ClassicInterface
}

class MyImplementation implements ClassicInterface {
    // The constructor looks like the signature described in Factory
    constructor(num: number, s: string) { } // obviously returns an instance of ClassicInterface
    method1() {}
    ...
    methodN() {}
}

class MyOtherImplementation implements ClassicInterface {
    // The constructor looks like the signature described in Factory
    constructor(n: number, s: string) { } // obviously returns an instance of ClassicInterface
    method1() {}
    ...
    methodN() {}
}

// And here is the polymorphism of construction
function instantiateClassicInterface(ctor: Factory, myNumberParam: number, myStringParam: string): ClassicInterface {
    return new ctor(myNumberParam, myStringParam);
}

// And this is how we do it
let iWantTheFirstImpl = instantiateClassicInterface(MyImplementation, 3.14, "smile");
let iWantTheSecondImpl = instantiateClassicInterface(MyOtherImplementation, 42, "vafli");

Răspunsul #4

Din documentația oficială

Acest lucru se datorează faptului că, atunci când o clasă implementează o interfață, este verificată doar partea de instanță a clasei. Deoarece constructorul se află în partea statică, acesta nu este inclus în această verificare.

În schimb, ar trebui să lucrați direct cu partea statică a clasei. În acest exemplu, definim două interfețe, ClockConstructor pentru constructor și ClockInterface pentru metodele de instanță. Apoi, pentru comoditate, definim o funcție constructor createClock care creează instanțe ale tipului care îi este transmis:

interface ClockConstructor {
  new (hour: number, minute: number): ClockInterface;
}

interface ClockInterface {
  tick(): void;
}

function createClock(
  ctor: ClockConstructor,
  hour: number,
  minute: number
): ClockInterface {
  return new ctor(hour, minute);
}

class DigitalClock implements ClockInterface {
  constructor(h: number, m: number) {}
  tick() {
    console.log("beep beep");
  }
}

class AnalogClock implements ClockInterface {
  constructor(h: number, m: number) {}
  tick() {
    console.log("tick tock");
  }
}

let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);