Typescript: Tambahkan Antarmuka dukungan untuk menentukan metode statis

Dibuat pada 13 Jan 2017  ·  43Komentar  ·  Sumber: microsoft/TypeScript

TypeScript Version: 2.0.3

Kode

interface Foo {
public static myStaticMethod(param1: any);
}

Perilaku yang diharapkan:
Tidak ada kesalahan
Perilaku sebenarnya:
Tidak didukung

Question

Komentar yang paling membantu

@aluanhaddad Saya menemukan kode Anda solusi dari sesuatu yang jelas.

interface JsonSerializable {
      public static fromJson(obj: any);
      public toJson(): string;
}

Mana yang Anda lihat dengan lebih jelas?

Semua 43 komentar

Apa yang ingin Anda capai dengan ini? dapatkah Anda menguraikan skenario tersebut?

Saya membayangkan kelas abstract adalah apa yang Anda cari.

@bayu_joo
Contoh:

interface JsonSerializable {
      public static fromJson(obj: any);
      public toJson(): string;
}

@rozzzly Dalam contoh ini, abstract class tidak valid.

Saya pikir ini akan sangat keren! Java menambahkan fungsionalitas ini di versi terakhir.

@Erginho
Saya pikir Anda mungkin menganggap ini menarik:

interface JsonSerializableStatic<C extends new (...args) => JsonSerializable<C>> {
  fromJson(json: string): JsonSerializable<C>;
}

interface JsonSerializable<C extends new (...args) => any> {
  toJson: () => string;
  constructor: C;
}

interface A extends JsonSerializable<typeof A> { }
class A implements JsonSerializable<typeof A> {

  constructor(readonly id: number, readonly name: string) { }
  toJson() { return JSON.stringify(this); }

  static fromJson(json: string): A {
    const obj = JSON.parse(json);
    return new A(obj.id, obj.name);
  }
}

const a = new A(1, 'Charlize');

const json = a.toJson();

const y = A.fromJson(json);
console.info(a, json, y);
console.info(new a.constructor(1, 'Theron'));
const m = new A.prototype.constructor(1, 'Charlize Theron');
console.info(m);

@aluanhaddad Saya menemukan kode Anda solusi dari sesuatu yang jelas.

interface JsonSerializable {
      public static fromJson(obj: any);
      public toJson(): string;
}

Mana yang Anda lihat dengan lebih jelas?

@aluanhaddad Saya menemukan kode Anda solusi dari sesuatu yang jelas.

interface JsonSerializable {
public static fromJson (obj: ada);
publik toJson (): string;
}
Mana yang Anda lihat dengan lebih jelas?

Memang, tetapi solusi saya bukanlah solusi, ini adalah pembenaran untuk kasus penggunaan Anda. Memiliki deserialisasi statis dan serialisasi instance yang diimplementasikan berdasarkan kelas demi kelas dan dengan demikian dienkapsulasi dan aman untuk diketik.
Deklarasi Anda:

interface JsonSerializable {
     public static fromJson(obj: any);
     public toJson(): string;
}

tidak relevan karena pada dasarnya mendeklarasikan ulang antarmuka objek JSON. Itu sama sekali tidak memerlukan metode antarmuka statis dengan cara apa pun.

Anda kehilangan konsep Antarmuka. class A implements JsonSerializable seharusnya membuat saya menerapkan kedua metode tersebut. Tapi sebenarnya membuat saya menerapkan:

toJson: () => string;
constructor: new (...args) => JsonSerializableStatic<C>;

Ini bukan solusi yang jelas.

Tidak ada alasan teknis untuk tidak mengizinkan untuk mendefinisikan metode statis pada antarmuka.

@Serginho Saya tidak berpendapat bahwa situasinya ideal. Saya hanya mencoba menggambarkan bahwa itu bisa diungkapkan.

@aluanhaddad Ayo! Buka pikiranmu. Apakah menurut Anda skrip ketikan harus mengizinkan metode statis di Antarmuka? ini diimplementasikan di Java 8 pada versi terakhir, jadi saya pikir saya tidak berbicara omong kosong.

@Serginho Saya tidak berpikir itu sangat cocok untuk TypeScript. Antarmuka harus menentukan fungsionalitas yang disediakan objek. Fungsionalitas ini harus dapat diganti dan dipertukarkan (itulah mengapa metode antarmuka bersifat virtual). Statika adalah konsep paralel dengan perilaku dinamis / metode virtual. Menjalin keduanya terasa tidak benar dari segi desain bagi saya.

Seperti @aluanhaddad sudah menulis, naskah sebenarnya sudah memiliki mekanisme untuk mengungkapkan apa yang Anda inginkan. Perbedaan besar adalah dalam kasus ini Anda memperlakukan kelas sebagai objek, yang terus konsisten secara logis. Dari sudut pandang saya, pendekatan yang Anda usulkan tidak terlalu cocok untuk TypeScript (dan pengembangan JavaScript) karena kelas adalah semacam warga kelas hack / kedua. Pola yang berlaku saat ini mengandalkan program ducktyping / shape, bukan pada kelas yang kaku.

Saya rasa, Anda juga dapat membuka pikiran dan mencoba gaya pemrograman yang berbeda. Dunia tidak dimulai atau diakhiri dengan OOP. Anda mungkin menemukan pemrograman dengan fungsi, objek biasa dan (dan pada tingkat tertentu) prototipe menyenangkan dan bahkan lebih baik. Gaya seperti itu jauh lebih sejalan dengan tujuan awal JavaScript dirancang. Di samping catatan, saya setuju JS perlahan-lahan beralih ke pola OOP-esque (setidaknya di tingkat komite), tapi itu karena dorongan besar dari orang-orang yang tidak nyaman dengan paradigma pemrograman dan teknik pengembangan yang berbeda. Bagi saya itu hal yang sangat menyedihkan dan mengecewakan.

Seperti yang dikatakan @gcnew , antarmuka mendeskripsikan bentuk objek individual, bukan kelasnya. Tapi tidak ada alasan kita tidak bisa menggunakan antarmuka untuk mendeskripsikan bentuk kelas itu sendiri, karena kelas juga objek.

import assert = require("assert");

interface JsonSerializableStatic<JsonType, InstanceType extends JsonSerializable<JsonType>> {
    fromJson(obj: JsonType): InstanceType;
}
interface JsonSerializable<JsonType> {
    toJson(): JsonType;
}

interface PointJson { x: number; y: number; }
class Point /*static implements JsonSerializableStatic<PointJson, Point>*/ {
    static fromJson(obj: PointJson): Point {
        return new Point(obj.x, obj.y)
    }

    constructor(readonly x: number, readonly y: number) {}

    toJson(): PointJson {
        return { x: this.x, y: this.y };
    }
}
// Hack for 'static implements'
const _: JsonSerializableStatic<PointJson, Point> = Point;

function testSerialization<JsonType, InstanceType extends JsonSerializable<JsonType>>(cls: JsonSerializableStatic<JsonType, InstanceType>, json: JsonType) {
    const instance: InstanceType = cls.fromJson(json);
    const outJson: JsonType = instance.toJson();
    assert.deepEqual(json, outJson);
}
testSerialization(Point, { x: 1, y: 2 });

Gagal memenuhi tanda tangan dari toJson atau fromJson mengakibatkan kesalahan kompilasi. (Sayangnya, kesalahannya adalah const _ daripada di metode.)

Mungkin terkait (karena berhubungan dengan pengetikan metode statis): # 5863.


@aluanhaddad : Contoh Anda memiliki kesalahan: JsonSerializable 's constructor anggota sebenarnya merujuk ke properti instance bernama constructor , yang ketika dipanggil dengan new akan mengembalikan JsonSerializableStatic . Artinya adalah bahwa (new ((new X()).constructor)).fromJson({}) harus berfungsi. Alasan mengapa berhasil dikompilasi adalah karena interface A extends JsonSerializable<typeof A> mendeklarasikan implementasi menjadi valid tanpa benar-benar memeriksanya. Misalnya, ini mengkompilasi tanpa kesalahan:

interface I { m(): void; }
interface A extends I { }
// No compile error
class A implements I { em() {} }

@Serginho Bukan pengguna Java, tetapi tampaknya bahasanya tidak memungkinkan Anda untuk menentukan antarmuka untuk sisi statis kelas (artinya kelas yang mengimplementasikan antarmuka harus menerapkan metode statis untuk menyesuaikannya). Java memungkinkan Anda untuk menentukan metode statis dengan tubuh dalam antarmuka, yang setara TypeScript adalah:

interface I { ... }
namespace I {
   export function interfaceStaticMethod() {}
}

Bagaimana jika Anda mencoba membuat pabrik generik?

interface Factorizable {
  static factory<U>(str: string): U
}

class Foo {
  private data: string[] = []
  bar<T extends Factorizable>(): T[] {
    return this.data.map(T.factory);
  }
}

class Bar implements Factorizable {
  static factory(str: string): Bar {
    // ...
  }
}

// Usage
var x = new Foo();
var y: Bar[] = x.bar();

Saya tidak yakin tentang sintaks interface saya usulkan di sini tapi saya tahu sintaks "penggunaan" adalah apa yang saya coba capai. Ini adalah pola yang sering saya gunakan di Swift (menggunakan protocols ) dan menurut saya akan sangat bagus di TypeScript. Meskipun bukan seorang desainer bahasa atau pelaksana kompilator, saya tidak yakin apakah itu sesuai dengan arah TypeScript yang dimaksudkan atau realistis untuk diterapkan.

Kelas tidak harus implement antarmuka. Anda cukup menentukan antarmuka, dan pemeriksaan tipe struktural akan menyimpan masalah apa pun di situs penggunaan, misalnya:

interface Factorizable<U> {
    factory(str: string): U
}

class Foo {
  private data: string[] = []
  bar<T>(factory: Factorizable<T>): T[] {
    return this.data.map(factory.factory);
  }
}

class Bar {
  static factory(str: string): Bar {
    // ...
  }
}

// Usage
var x = new Foo();
var y = x.bar(Bar); // Bar[]

@mhegazy itu adalah solusi yang relatif bagus. Terima kasih telah menyediakan itu! 🙏

Masih ada dua hal yang membuat saya tidak nyaman (memang begitu juga show stoppers):

  1. Kami masih tidak dapat menyatakan bahwa Bar secara eksplisit mengimplementasikan atau sesuai dengan Factorizable .

    • Dalam praktiknya, saya membayangkan ini sebenarnya bukan masalah. Karena Memang benar bahwa jika antarmuka Factorizable berubah dengan cara yang tidak kompatibel, penggunaan x.bar(Bar) akan mulai salah dan kemudian Anda akan memperbaiki perubahan drift.
  2. Bagi saya, ini masih merupakan beban kognitif untuk menyatakan tipe y di sisi kanan tugas.

    • Yang lebih aneh lagi, sintaks ini akan mengaktifkan perilaku ini: var y: Baz[] = x.bar(Bar) . Jelas, sebuah kesalahan, tetapi sintaksnya memungkinkan pengembang untuk membatasi masalah secara berlebihan dengan mendefinisikan tipe kembalian di dua tempat.

Kami masih tidak dapat menyatakan bahwa Bar secara eksplisit mengimplementasikan atau sesuai dengan Factorizable.

Ada dua jenis yang terlibat, 1. fungsi konstruktor (misalnya sisi statis kelas), dan 2. sisi instance (apa yang keluar saat memanggil new ). Mencampur keduanya dalam satu jenis tidak benar. Secara teori Anda dapat memiliki implements dan static implements tetapi dalam praktiknya, seperti yang Anda catat, ini jarang digunakan, dan klausa implements benar-benar tidak menambahkan banyak. Pemeriksaan dilakukan di situs penggunaan dengan cara apa pun terlepas dari apakah Anda memiliki klausa implements atau tidak.

Bagi saya, menyatakan tipe y di sisi kanan tugas masih merupakan beban kognitif.

Keduanya memiliki arti yang berbeda, var y = x.bar(Bar) mendeklarasikan variabel baru y dengan tipe yang sama seperti x.bar(Bar) ; dimana var y: Bar[] = x.bar(Bar) mendeklarasikan variabel baru y dengan tipe Bar[] dan memverifikasi bahwa tipe x.bar(Bar) dapat dialihkan ke Bar[] .

Memiliki siad itu, ini lebih merupakan masalah gaya. Secara pribadi, rekomendasi saya adalah untuk tidak menggunakan anotasi tipe secara eksplisit kecuali Anda harus melakukannya; biarkan tipe mengalir melalui sistemnya. Saya telah melihat basis kode, bagaimanapun, di mana panduan gayanya adalah sebaliknya, di mana semuanya memiliki anotasi tipe eksplisit.

@mhegazy terima kasih atas diskusi / perspektifnya.

@ andy-hanson Terima kasih telah meluangkan waktu untuk mengoreksi saya. Saya memperbaiki kesalahan dalam contoh saya.

Ini juga berfungsi, dan menampilkan kesalahan pada waktu kompilasi tanpa pemanggilan fungsi tambahan:

interface Type<T> {
    new (...args: any[]): T;
}

/* static interface declaration */
interface ComparableStatic<T> extends Type<Comparable<T>> {
    compare(a: T, b: T): number;
}

/* interface declaration */
interface Comparable<T> {
    compare(a: T): number;
}

/* class decorator */
function staticImplements<T>() {
    return (constructor: T) => {}
}

@staticImplements<ComparableStatic<TableCell>>()   /* this statement implements both normal interface & static interface */
class TableCell { /* implements Comparable<TableCell> { */  /* not required. become optional */
    value: number;

    compare(a: TableCell): number {
        return this.value - a.value;
    }

    static compare(a: TableCell, b: TableCell): number {
        return a.value - b.value;
    }
}

Apa hasil diskusinya?

Saya mengalami ini dan saya juga ingin menggunakan static interface :

interface IDb {
  public static instance: () => Db,
}

Kebanyakan orang lupa bahwa sudah ada antarmuka _static_ dalam arti bahwa fungsi / kelas konstruktor sudah memiliki dua antarmuka, antarmuka konstruktor dan antarmuka instance.

interface MyFoo {
  method(): void;
}

interface MyFooConstructor {
  new (): MyFoo;
  prototype: MyFoo;
  staticMethod(): any;
}

const MyFoo = function MyFoo() {
  this.prop = '';
} as any as MyFooConstructor;

MyFoo.prototype = {
  method() { console.log(this); }
}

MyFoo.staticMethod = function () { /* do something static */ }

Jika Anda tidak menggunakan kelas _abstract_, Anda sudah memiliki kekuatan.

Terima kasih @kitsonk untuk membalas.

Deklarasi Anda tampaknya harus berfungsi, tetapi terlalu bertele-tele untuk kasus ini.

Dan saya baru saja mencoba kelas _abstract_, tetapi tampaknya tidak mendukung static dengan abstract .

[ts] 'static' modifier cannot be used with 'abstract' modifier.

@zixia itu masalah # 14600

Ya, mari kita pilih.

Adakah yang mau menjawab dengan solusi untuk pertanyaan saya di sini: http://stackoverflow.com/questions/44047874/dynamically-modify-typescript-classes-through-a-generic-function

Saya pikir seluruh masalah akan diselesaikan dengan dapat menentukan anggota statis pada antarmuka, dan jika masalah ini ditutup karena tidak diperlukan , saya sangat ingin melihat cara mengatasinya.

@grantila Saya telah menjawab pertanyaan Anda. Seperti yang disebutkan sebelumnya dalam masalah ini, kecuali Anda memiliki persyaratan lebih lanjut yang tidak disebutkan di sana, ini mudah dipecahkan dengan memperlakukan kelas sebagai objek.

@ Enet4 Saya memperbarui pertanyaan, itu terlalu disederhanakan. Masalah sebenarnya tidak dapat diperbaiki dengan peretasan Object.defineProperty() sayangnya. Yang btw, adalah retasan. Saya ingin memastikan untuk tidak salah mengeja make - pada dasarnya pemeriksaan statis yang tepat.

Ini adalah masalah nyata yang saya miliki, kode yang saya mulai porting ke TypeScript tetapi sekarang disimpan sebagai JavaScript, karena saat ini saya tidak dapat menulis ulang kode dalam jumlah besar seperti yang diperlukan jika tidak.

Saya ingin memastikan agar tidak salah eja make - pada dasarnya pemeriksaan statis yang tepat.

Pemeriksaan statis hanya dapat dilakukan sejauh ini. Tetapi jika satu-satunya perhatian Anda sekarang adalah Anda menyetel properti yang tepat, maka mentransmisikan ke jenis pembuat dan menyetel properti dari sana tampaknya mengatasinya.

@ Enet4 itu solusi yang berhasil, terima kasih. Saya pikir masalah ini (13462) harus dilihat lagi, karena solusi seperti milik Anda, menggunakan tipe casting, sebenarnya bukan tipe aman, dan jika ini adalah satu-satunya cara untuk menyelesaikan situasi bekerja dengan tipe kelas sebagai nilai, kami kehilangan banyak fleksibilitas bahasa dinamis.

solusi seperti milik Anda, menggunakan jenis casting, sebenarnya bukan jenis aman, dan jika ini adalah satu-satunya cara untuk menyelesaikan situasi bekerja dengan jenis kelas sebagai nilai, kami kehilangan banyak fleksibilitas dari bahasa dinamis.

@grantila Dalam pembelaan saya, itu bisa diperdebatkan. : wink: Kasus penggunaan Anda berbeda dari yang disajikan dalam masalah ini, karena jenis kelas Anda mungkin (atau tidak) menyediakan metode bergantung pada kondisi waktu proses. Dan IMO yang lebih tidak aman daripada jenis cor yang disajikan dalam jawaban saya, yang hanya dilakukan untuk memungkinkan penyisipan bidang dalam suatu objek. Dalam hal ini, jenis kelas yang dihasilkan C & Maker<T> harus tetap kompatibel dengan semua hal lain yang mengandalkan C .

Saya juga mencoba membayangkan di mana metode statis dalam antarmuka dapat membantu Anda di sini, tetapi saya mungkin melewatkan sesuatu. Bahkan jika Anda memiliki sesuatu seperti static make?(... args: any[]): self di antarmuka Anda, itu harus diperiksa dalam runtime untuk keberadaan sebelum panggilan. Jika Anda ingin melanjutkan diskusi ini, mari pertimbangkan untuk melakukannya di tempat lain untuk mengurangi kebisingan. : sedikit_muka_senyum:

Jadi kita tidak bisa mengetik periksa metode pabrik statis di kelas yang menerapkan antarmuka yang sama?

Kasus penggunaan saya adalah:

interface IObject {
    static make(s: string): IObject;
}

class A implements IObject{
    static make(s: string): IObject {
        // Implementation A...
    }
}

class B implements IObject{
    static make(s: string): IObject {
        // Implementation B...
    }
}

A.make("string"); // returns A
B.make("string"); // returns B

Saya tidak ingin menulis kelas pabrik baru hanya untuk ini.

@ tyteen4a03 Hapus IObject dari contoh itu, dan itu akan dikompilasi. Lihat juga https://github.com/Microsoft/TypeScript/issues/17545#issuecomment -319422545

@ andy-ms Ya jelas itu berhasil tetapi inti dari pemeriksaan jenis adalah untuk .. memeriksa jenis. Anda selalu dapat menurunkan keamanan jenis cukup jauh untuk membuat setiap kasus penggunaan dikompilasi, tetapi itu mengabaikan fakta bahwa ini adalah permintaan fitur dan tidak terlalu gila pada saat itu.

Ya jelas itu berhasil tetapi inti dari pemeriksaan tipe adalah untuk .. memeriksa jenis.

Ini adalah utas panjang yang panjang tentang bagaimana sisi statis kelas adalah antarmuka terpisah ke sisi instance dan implements menunjuk ke sisi instance. @ andy-ms menunjukkan ke @ tyteen4a03 cara mendapatkan potongan kode yang berfungsi, karena itu _wrong_, bukan untuk mengabaikan pemeriksaan jenis.

Kasus penggunaan saya untuk mengizinkan metode statis menggunakan parameter generik kelas adalah untuk kelas mixin. Saya membangun kerangka entitas yang menggunakan anotasi untuk menentukan kolom, database, dll dan ingin memiliki fungsi statis yang dicampur ke dalam kelas entitas saya yang akan memungkinkan akses mudah ke repositori yang diketik dengan benar.

class RepositoryMixin<T> {
    public static repository(): EntityRepository<T> {
        return new EntityRepository<T>(Object.getPrototypeOf(this));
    }
}

@mixin(RepositoryMixin)
class Entity implements RepositoryMixin<Entity> {
    public id: number;
}

Entity.repository().save(new Entity());

@rmblstrp Dapatkah Anda menunjukkan bagaimana Anda akan menggunakan fitur yang diusulkan dalam contoh Anda? Lebih disukai sebagai sesuatu yang dapat diverifikasi?

@rmblstrp yang tidak membutuhkan fitur ini. Faktanya, karena Anda menggunakan dekorator, Anda sebenarnya bisa _tipe_nya untuk memverifikasi bahwa kelas beranotasi menyediakan metode statis yang diperlukan. Anda tidak membutuhkan keduanya dan itu akan menjadi berlebihan.

Halo, saya tidak ingin keluar dari topik atau cakupan percakapan ini.
Namun, karena Anda membawa diskusi paradigma pemrograman yang berbeda (haruskah kita menggunakan OOP atau fungsional?), Saya ingin berbicara secara khusus tentang pabrik statis yang biasanya digunakan untuk membuat koneksi ke db atau menyediakan beberapa jenis layanan.
Dalam banyak bahasa seperti PHP dan Java, pabrik statis sudah tidak digunakan lagi karena mendukung Injeksi Ketergantungan. Kontainer Dependency Injection dan IOC telah menjadi populer berkat kerangka kerja seperti Symfony dan Spring.
Typecript memiliki wadah IOC yang bagus bernama InversifyJS.

Ini adalah file di mana Anda dapat melihat bagaimana semuanya ditangani.
https://github.com/Deviad/virtual-life/blob/master/models/generic.ts
https://github.com/Deviad/virtual-life/blob/master/service/user.ts
https://github.com/Deviad/virtual-life/blob/master/models/user.ts
https://github.com/Deviad/virtual-life/blob/master/utils/sqldb/client.ts
https://github.com/Deviad/virtual-life/blob/master/bootstrap.ts

Saya tidak mengatakan ini adalah solusi yang tepat (dan mungkin itu meninggalkan beberapa skenario yang tidak terungkap) tetapi bekerja juga dengan React, ada beberapa contoh yang sudah dapat Anda lihat.

Selain itu, saya sarankan Anda untuk melihat video tentang pemrograman fungsional yang mencakup aspek ini tentang mana yang lebih baik: https://www.youtube.com/watch?v=e-5obm1G_FY&t=1487s
SPOILER: tidak ada yang lebih baik, itu tergantung pada masalah yang ingin Anda selesaikan. Jika Anda berurusan dengan pengguna, ruang kelas, guru OOP akan lebih baik untuk memodelkan masalah Anda menggunakan objek.
Jika Anda membutuhkan parser yang memindai situs web mungkin fungsional bisa lebih baik menggunakan generator fungsi yang mengembalikan hasil parsial, dll. :)

@Deviad @aluanhaddad Sejak posting saya, saya telah menggunakan InversifyJS dan itu benar-benar hebat dan pasti cara yang jauh lebih baik. Pada saat posting saya, saya baru saja mulai menggunakan Typecript / Node setelah sebelumnya PHP / C #. Hanya butuh sedikit waktu untuk terbiasa dengan lingkungan dan paket yang tersedia.

Apa status ini? Mengapa Anda terus menutup masalah yang belum terselesaikan di repo?

Saya yakin ini adalah salah satu kasus di mana masalah harus secara eksplisit diberi label dengan "wontfix", karena pilihan untuk tidak memiliki metode statis dalam antarmuka adalah dengan desain.

@ enet4 , saya seorang pendatang baru, tapi itu sama sekali tidak jelas. Membaca ini dan masalah terkait lainnya, sepertinya sebagian besar masalah berikut:

A. Sulit
B. Kami tidak menyukai (setiap) sintaks tertentu yang telah kami lihat sejauh ini.
C. Sebuah minoritas kecil tidak percaya itu harus dilakukan sama sekali, dan lebih suka menghapus cara aneh saat ini untuk melakukannya.

Jika benar-benar sesuai desain, dan mereka yang bertanggung jawab tidak menginginkannya, mereka harus menulis dokumen publik, dan menghubungkannya ke utas ini dan utas lainnya. Ini akan menghemat banyak waktu untuk membuat kita berada dalam ketidakpastian.

Kami telah menautkan ke # 14600 di utas ini dan itu adalah masalah yang harus diikuti untuk permintaan fitur.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat