typescript - Bagaimana antarmuka dengan tanda tangan konstruksi bekerja?


Saya mengalami beberapa kesulitan dalam memahami bagaimana mendefinisikan konstruktor dalam antarmuka bekerja. Saya mungkin sepenuhnya salah memahami sesuatu. Tetapi saya telah mencari jawaban untuk waktu yang cukup lama dan saya tidak dapat menemukan apa pun yang terkait dengan ini.

Bagaimana saya mengimplementasikan antarmuka berikut dalam kelas TypeScript:

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

Anders Hejlsberg membuat sebuah antarmuka yang berisi sesuatu yang mirip dengan ini dalam video ini (sekitar menit ke-14). Tapi saya sama sekali tidak bisa mengimplementasikan ini dalam sebuah kelas.

Mungkin saya salah paham sesuatu, apa yang tidak saya mengerti?

EDIT :

Untuk memperjelas. Dengan "new ( ... )" saya maksudkan "apa saja". Masalah saya adalah saya bahkan tidak bisa membuat versi paling dasar dari ini bekerja:

interface MyInterface {
    new () : MyInterface;
}

class test implements MyInterface {
    constructor () { }
}

Ini tidak berhasil dikompilasi untuk saya, saya mendapatkan "Kelas 'test' mendeklarasikan antarmuka 'MyInterface' tetapi tidak mengimplementasikannya: Tipe 'MyInterface' membutuhkan tanda tangan konstruktor, tetapi Tipe 'test' tidak memilikinya" saat mencoba mengompilasinya.

EDIT :

Jadi setelah meneliti ini sedikit lebih lanjut mengingat umpan balik yang diberikan.

interface MyInterface {
    new () : MyInterface;
}

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

Bukan TypeScript yang valid dan ini tidak menyelesaikan masalah. Anda tidak bisa mendefinisikan tipe pengembalian dari constructor. Itu akan mengembalikan "test". Tanda tangan dari yang berikut: class test { constructor() { } } Tampaknya adalah "new() => test" (diperoleh dengan mengarahkan kursor ke "class" di editor online hanya dengan kode itu ditempelkan). Dan inilah yang kita inginkan dan yang saya kira akan terjadi.

Apakah ada yang bisa memberikan contoh dari ini atau sesuatu yang serupa di mana ini benar-benar dapat dikompilasi?

SUNGGUHAN (lagi...):

Jadi saya mungkin telah menemukan ide tentang mengapa ini bisa didefinisikan di sebuah interface tetapi tidak bisa diimplementasikan di kelas TypeScript. Berikut ini berhasil:

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

interface MyInterface {
    new () : MyInterface;
}

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

Jadi apakah ini hanya fitur TypeScript yang memungkinkan Anda menghubungkan JavaScript? Atau apakah memungkinkan untuk mengimplementasikannya di TypeScript tanpa harus mengimplementasikan kelas menggunakan JavaScript?

Jawaban #1

Tanda tangan konstruktor dalam antarmuka tidak dapat diimplementasikan dalam kelas; mereka hanya untuk mendefinisikan API JS yang ada yang mendefinisikan fungsi yang bisa 'new'. Berikut adalah contoh yang melibatkan tanda tangan new pada antarmuka yang memang berfungsi:

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);

Ini menciptakan batasan nyata untuk apa yang bisa Anda panggil dengan makeObj:

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

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

Jawaban #2

Dalam pencarian saya untuk pertanyaan yang sama persis, saya mencari bagaimana Tim TypeScript melakukan itu...

Mereka sedang mendeklarasikan sebuah antarmuka dan setelah itu sebuah variabel dengan nama yang persis sama dengan nama antarmuka. Ini juga merupakan cara untuk mengetik fungsi statis.

Contoh dari lib.d.ts:

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

Saya mencobanya dan itu berhasil dengan baik.

Jawaban #3

Nah, sebuah antarmuka dengan tanda tangan konstruk tidak dimaksudkan untuk diimplementasikan oleh kelas mana pun (pada pandangan pertama ini mungkin terlihat aneh bagi orang-orang dengan latar belakang C#/Java seperti saya, tetapi beri kesempatan). Itu sedikit berbeda.

Untuk sesaat, bayangkan itu sebagai antarmuka dengan tanda panggilan (seperti @FunctionalInterface dalam dunia Java). Tujuannya adalah untuk menggambarkan tipe fungsi...semacam itu. Tanda yang digambarkan seharusnya dipenuhi oleh objek fungsi...tetapi bukan sembarang fungsi tingkat tinggi atau metode. Fungsi tersebut haruslah fungsi yang tahu bagaimana membangun sebuah objek, fungsi yang dipanggil ketika kata kunci new digunakan.

Jadi, sebuah interface dengan tanda tangan konstruktor mendefinisikan tanda tangan dari sebuah konstruktor! Konstruktor dari kelas Anda harus mematuhi tanda tangan yang didefinisikan dalam interface (anggaplah ini seperti konstruktor mengimplementasikan interface). Itu seperti sebuah pabrik!

Berikut adalah cuplikan kode pendek yang mencoba menunjukkan penggunaan yang paling umum:

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");

Jawaban #4

Dari dokumentasi resmi

Ini karena ketika sebuah kelas mengimplementasikan antarmuka, hanya sisi instance dari kelas yang diperiksa. Karena konstruktor berada di sisi statis, itu tidak termasuk dalam pemeriksaan ini.

Sebaliknya, Anda perlu bekerja langsung dengan sisi statis dari kelas. Dalam contoh ini, kami mendefinisikan dua antarmuka, ClockConstructor untuk konstruktor dan ClockInterface untuk metode instance. Kemudian, untuk kemudahan, kami mendefinisikan sebuah fungsi konstruktor createClock yang membuat instance dari tipe yang diberikan kepadanya:

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);

Artikel terkait