Typescript: Saran: Tambahkan metode statis abstrak di kelas dan metode statis di antarmuka

Dibuat pada 12 Mar 2017  ·  98Komentar  ·  Sumber: microsoft/TypeScript

Sebagai masalah lanjutan #2947, yang mengizinkan pengubah abstract pada deklarasi metode tetapi melarangnya pada deklarasi metode statis, saya menyarankan untuk memperluas fungsionalitas ini ke deklarasi metode statis dengan mengizinkan pengubah abstract static pada deklarasi metode .

Masalah terkait menyangkut pengubah static pada deklarasi metode antarmuka, yang tidak diizinkan.

1. Masalahnya

1.1. Metode statis abstrak di kelas abstrak

Dalam beberapa kasus menggunakan kelas abstrak dan implementasinya, saya mungkin perlu memiliki beberapa nilai yang bergantung pada kelas (bukan bergantung pada instans), yang harus diakses dalam konteks kelas anak (bukan dalam konteks objek), tanpa menciptakan sebuah objek. Fitur yang memungkinkan melakukan ini adalah pengubah static pada deklarasi metode.

Misalnya (contoh 1):

abstract class AbstractParentClass {
}

class FirstChildClass extends AbstractParentClass {

    public static getSomeClassDependentValue(): string {
        return 'Some class-dependent value of class FirstChildClass';
    }
}

class SecondChildClass extends AbstractParentClass {

    public static getSomeClassDependentValue(): string {
        return 'Some class-dependent value of class SecondChildClass';
    }
}

FirstChildClass.getSomeClassDependentValue(); // returns 'Some class-dependent value of class FirstChildClass'
SecondChildClass.getSomeClassDependentValue(); // returns 'Some class-dependent value of class SecondChildClass'

Tetapi dalam beberapa kasus saya juga perlu mengakses nilai ini ketika saya hanya tahu bahwa kelas pengakses diwarisi dari AbstractParentClass, tetapi saya tidak tahu kelas anak mana yang saya akses. Jadi saya ingin memastikan, bahwa setiap anak dari AbstractParentClass memiliki metode statis ini.

Misalnya (contoh 2):

abstract class AbstractParentClass {
}

class FirstChildClass extends AbstractParentClass {

    public static getSomeClassDependentValue(): string {
        return 'Some class-dependent value of class FirstChildClass';
    }
}

class SecondChildClass extends AbstractParentClass {

    public static getSomeClassDependentValue(): string {
        return 'Some class-dependent value of class SecondChildClass';
    }
}

abstract class AbstractParentClassFactory {

    public static getClasses(): (typeof AbstractParentClass)[] {
        return [
            FirstChildClass,
            SecondChildClass
        ];
    }
}

var classes = AbstractParentClassFactory.getClasses(); // returns some child classes (not objects) of AbstractParentClass

for (var index in classes) {
    if (classes.hasOwnProperty(index)) {
        classes[index].getSomeClassDependentValue(); // error: Property 'getSomeClassDependentValue' does not exist on type 'typeof AbstractParentClass'.
    }
}

Akibatnya, kompiler memutuskan bahwa terjadi kesalahan dan menampilkan pesan: Properti 'getSomeClassDependentValue' tidak ada pada tipe 'typeof AbstractParentClass'.

1.2. Metode statis dalam antarmuka

Dalam beberapa kasus, logika antarmuka menyiratkan bahwa kelas pelaksana harus memiliki metode statis, yang memiliki daftar parameter yang telah ditentukan dan mengembalikan nilai tipe yang tepat.

Misalnya (contoh 3):

interface Serializable {
    serialize(): string;
    static deserialize(serializedValue: string): Serializable; // error: 'static' modifier cannot appear on a type member.
}

Saat mengkompilasi kode ini, terjadi kesalahan: pengubah 'statis' tidak dapat muncul pada anggota tipe.

2. Solusinya

Solusi untuk kedua masalah (1.1 dan 1.2) adalah mengizinkan pengubah abstract pada deklarasi metode statis di kelas abstrak dan pengubah static di antarmuka.

3. Implementasi JS

Implementasi fitur ini dalam JavaScript harus serupa dengan implementasi antarmuka, metode abstrak, dan metode statis.

Ini berarti bahwa:

  1. Mendeklarasikan metode statis abstrak dalam kelas abstrak seharusnya tidak memengaruhi representasi kelas abstrak dalam kode JavaScript.
  2. Mendeklarasikan metode statis di antarmuka seharusnya tidak memengaruhi representasi antarmuka dalam kode JavaScript (tidak ada).

Misalnya, kode TypeScript ini (contoh 4):

interface Serializable {
    serialize(): string;
    static deserialize(serializedValue: string): Serializable;
}

abstract class AbstractParentClass {
    public abstract static getSomeClassDependentValue(): string;
}

class FirstChildClass extends AbstractParentClass {

    public static getSomeClassDependentValue(): string {
        return 'Some class-dependent value of class FirstChildClass';
    }
}

class SecondChildClass extends AbstractParentClass implements Serializable {

    public serialize(): string {
        var serialisedValue: string;
        // serialization of this
        return serialisedValue;
    }

    public static deserialize(serializedValue: string): SecondChildClass {
        var instance = new SecondChildClass();
        // deserialization
        return instance;
    }

    public static getSomeClassDependentValue(): string {
        return 'Some class-dependent value of class SecondChildClass';
    }
}

harus dikompilasi ke kode JS ini:

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

var FirstChildClass = (function (_super) {
    __extends(FirstChildClass, _super);
    function FirstChildClass() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    FirstChildClass.getSomeClassDependentValue = function () {
        return 'Some class-dependent value of class FirstChildClass';
    };
    return FirstChildClass;
}(AbstractParentClass));

var SecondChildClass = (function (_super) {
    __extends(SecondChildClass, _super);
    function SecondChildClass() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    SecondChildClass.prototype.serialize = function () {
        var serialisedValue;
        // serialization of this
        return serialisedValue;
    };
    SecondChildClass.deserialize = function (serializedValue) {
        var instance = new SecondChildClass();
        // deserialization
        return instance;
    };
    SecondChildClass.getSomeClassDependentValue = function () {
        return 'Some class-dependent value of class SecondChildClass';
    };
    return SecondChildClass;
}(AbstractParentClass));

4. Poin yang relevan

  • Deklarasi metode statis abstrak dari kelas abstrak harus ditandai dengan pengubah abstract static
  • Implementasi metode abstract static dari kelas abstrak harus ditandai dengan abstract static atau static modifier
  • Deklarasi metode statis antarmuka harus ditandai dengan pengubah static
  • Implementasi metode statik dari sebuah antarmuka harus ditandai dengan pengubah static

Semua properti lain dari pengubah abstract static harus diwarisi dari properti pengubah abstract dan static .

Semua properti lain dari static pengubah metode antarmuka harus diwarisi dari metode antarmuka dan properti pengubah static .

5. Daftar Periksa Fitur Bahasa

  • sintaksis

    • _Apa tata bahasa fitur ini?_ - Tata bahasa fitur ini adalah abstract static pengubah metode kelas abstrak dan static pengubah metode antarmuka.

    • _Apakah ada implikasi untuk back-compat JavaScript? Jika demikian, apakah mereka cukup dimitigasi?_ - Tidak ada implikasi untuk back-compat JavaScript.

    • _Apakah sintaks ini mengganggu perubahan ES6 atau ES7 yang masuk akal?_ - Sintaks ini tidak mengganggu perubahan ES6 atau ES7 yang masuk akal.

  • semantik

    • _Apa yang dimaksud dengan kesalahan di bawah fitur yang diusulkan?_ - Sekarang ada kesalahan saat mengkompilasi abstract static pengubah metode kelas abstrak dan static pengubah metode antarmuka.

    • _Bagaimana fitur memengaruhi hubungan subtipe, supertipe, identitas, dan penetapan?_ - Fitur tidak memengaruhi hubungan subtipe, supertipe, identitas, dan penetapan.

    • _Bagaimana fitur berinteraksi dengan obat generik?_ - Fitur tidak berinteraksi dengan obat generik.

  • Memancarkan

    • _Apa efek fitur ini pada emisi JavaScript?_ - Tidak ada efek fitur ini pada emisi JavaScript.

    • _Apakah ini memancarkan dengan benar di hadapan variabel tipe 'apa saja'?_ - Ya.

    • _Apa dampaknya terhadap file deklarasi (.d.ts) yang dipancarkan?_ - Tidak ada dampak terhadap file deklarasi.

    • _Apakah fitur ini berfungsi baik dengan modul eksternal?_ - Ya.

  • Kesesuaian

    • _Apakah ini perubahan yang melanggar dari kompiler 1.0?_ - Mungkin ya, kompiler 1.0 tidak akan dapat mengkompilasi kode yang mengimplementasikan fitur ini.

    • _Apakah ini perubahan besar dari perilaku JavaScript?_ - Tidak.

    • _Apakah ini implementasi yang tidak kompatibel dari fitur JavaScript masa depan (yaitu ES6/ES7/lebih baru)?_ - Tidak.

  • Lainnya

    • _Dapatkah fitur tersebut diimplementasikan tanpa mempengaruhi kinerja kompiler secara negatif?_ - Mungkin ya.

    • _Apa pengaruhnya pada skenario perkakas, seperti penyelesaian anggota dan bantuan tanda tangan di editor?_ - Mungkin tidak ada dampak jenis ini.

Awaiting More Feedback Suggestion

Komentar yang paling membantu

abstract class Serializable {  
    abstract serialize (): Object;  
    abstract static deserialize (Object): Serializable;  
}  

Saya ingin memaksakan implementasi metode deserialize statis di subclass Serializable.
Apakah ada solusi untuk menerapkan perilaku seperti itu?

Semua 98 komentar

Metode antarmuka statis umumnya tidak masuk akal, lihat #13462

Menunda ini sampai kami mendengar lebih banyak umpan balik tentang itu.

Sebuah pertanyaan besar yang kami miliki ketika mempertimbangkan ini: Siapa yang diizinkan untuk memanggil metode abstract static ? Agaknya Anda tidak dapat memanggil AbstractParentClass.getSomeClassDependentValue secara langsung. Tetapi bisakah Anda memanggil metode pada ekspresi tipe AbstractParentClass ? Jika demikian, mengapa itu harus diizinkan? Jika tidak, apa gunanya fitur tersebut?

Metode antarmuka statis umumnya tidak masuk akal, lihat #13462

Dalam diskusi #13462 saya tidak melihat mengapa metode antarmuka static tidak masuk akal. Saya hanya melihat bahwa fungsinya dapat diimplementasikan dengan cara lain (yang membuktikan bahwa mereka tidak masuk akal).

Dari sudut pandang praktis, antarmuka adalah semacam spesifikasi - seperangkat metode tertentu yang wajib untuk implementasinya di kelas yang mengimplementasikan antarmuka ini. Antarmuka tidak hanya mendefinisikan fungsionalitas yang disediakan objek, tetapi juga semacam kontrak.

Jadi saya tidak melihat alasan logis mengapa class mungkin memiliki metode static dan interface tidak.

Jika kita mengikuti sudut pandang bahwa segala sesuatu yang sudah dapat diimplementasikan dalam bahasa tidak memerlukan perbaikan sintaks dan hal-hal lain (yaitu, argumen ini adalah salah satu poin utama dalam pembahasan #13462), maka dipandu oleh ini sudut pandang, kita dapat memutuskan bahwa siklus while redundan karena dapat diimplementasikan menggunakan for dan if bersama-sama. Tapi kita tidak akan menyingkirkan while .

Sebuah pertanyaan besar yang kami miliki ketika mempertimbangkan ini: Siapa yang diizinkan untuk memanggil metode abstract static ? Agaknya Anda tidak dapat memanggil AbstractParentClass.getSomeClassDependentValue secara langsung. Tetapi bisakah Anda memanggil metode pada ekspresi tipe AbstractParentClass ? Jika demikian, mengapa itu harus diizinkan? Jika tidak, apa gunanya fitur tersebut?

Pertanyaan bagus. Karena Anda sedang mempertimbangkan masalah ini, bisakah Anda membagikan ide Anda tentang ini?

Baru terpikir oleh saya bahwa pada tingkat kompiler kasus panggilan langsung AbstractParentClass.getSomeClassDependentValue tidak akan dilacak (karena tidak dapat dilacak), dan kesalahan runtime JS akan terjadi. Tapi saya tidak yakin apakah ini konsisten dengan ideologi TypeScript.

Saya hanya melihat bahwa fungsinya dapat diimplementasikan dengan cara lain (yang membuktikan bahwa mereka tidak masuk akal).

Hanya karena sesuatu bisa diterapkan, bukan berarti itu masuk akal. 😉.

abstract class Serializable {  
    abstract serialize (): Object;  
    abstract static deserialize (Object): Serializable;  
}  

Saya ingin memaksakan implementasi metode deserialize statis di subclass Serializable.
Apakah ada solusi untuk menerapkan perilaku seperti itu?

Apa pendapat terbaru tentang ini? Saya baru saja mencoba menulis properti statis abstrak dari kelas abstrak dan benar-benar terkejut ketika itu tidak diizinkan.

Apa yang dikatakan @patryk-zielinski93. Berasal dari beberapa tahun proyek di PHP yang kami konversi ke TS, kami DO ingin metode statis di antarmuka.

Kasus penggunaan yang sangat umum ini akan membantu adalah komponen React, yang merupakan kelas dengan properti statis seperti displayName , propTypes , dan defaultProps .

Karena keterbatasan ini, pengetikan untuk React saat ini mencakup dua jenis: kelas Component , dan antarmuka ComponentClass termasuk fungsi konstruktor dan properti statis.

Untuk mengetik sepenuhnya, periksa komponen Bereaksi dengan semua properti statisnya, satu memiliki dua gunakan kedua jenis.

Contoh tanpa ComponentClass : properti statis diabaikan

import React, { Component, ComponentClass } from 'react';

type Props = { name: string };

{
  class ReactComponent extends Component<Props, any> {
    // expected error, but got none: displayName should be a string
    static displayName = 1
    // expected error, but got none: defaultProps.name should be a string
    static defaultProps = { name: 1 }
  };
}

Contoh dengan ComponentClass : properti static dicentang type

{
  // error: displayName should be a string
  // error: defaultProps.name should be a string
  const ReactComponent: ComponentClass<Props> = class extends Component<Props, any> {
    static displayName = 1
    static defaultProps = { name: 1 }
  };
}

Saya menduga banyak orang saat ini tidak menggunakan ComponentClass , tidak menyadari bahwa properti statis mereka tidak diperiksa tipenya.

Masalah terkait: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/16967

Apakah ada kemajuan dalam hal ini? Atau ada solusi untuk konstruktor di kelas abstrak?
Berikut ini contohnya:

abstract class Model {
    abstract static fromString(value: string): Model
}

class Animal extends Model {
    constructor(public name: string, public weight: number) {}

    static fromString(value: string): Animal {
        return new Animal(...JSON.parse(value))
    }
}

@roboslone

class Animal {
    static fromString(value: string): Animal {
        return new Animal();
    }
}

function useModel<T>(model: { fromString(value: string): T }): T {
    return model.fromString("");
}

useModel(Animal); // Works!

Setuju bahwa ini adalah fitur yang sangat kuat dan berguna. Menurut saya, fitur inilah yang membuat kelas menjadi 'warga negara kelas satu'. Warisan metode kelas/statis dapat dan memang masuk akal, terutama untuk pola pabrik metode statis, yang telah dipanggil di sini oleh poster lain beberapa kali. Pola ini sangat berguna untuk deserialisasi, yang merupakan operasi yang sering dilakukan di TypeScript. Misalnya, sangat masuk akal jika ingin mendefinisikan antarmuka yang menyediakan kontrak yang menyatakan bahwa semua tipe implementasi dapat dibuat dari JSON.

Tidak mengizinkan metode pabrik statis abstrak mengharuskan pelaksana untuk membuat kelas pabrik abstrak sebagai gantinya, tidak perlu menggandakan jumlah definisi kelas. Dan, seperti yang ditunjukkan oleh poster lain, ini adalah fitur yang kuat dan sukses yang diimplementasikan dalam bahasa lain, seperti PHP dan Python.

Baru mengenal TypeScript, tetapi saya juga terkejut ini tidak diizinkan secara default, dan begitu banyak orang mencoba membenarkan untuk tidak menambahkan fitur dengan:

  1. TS tidak memerlukan fitur tersebut, karena Anda masih dapat mencapai apa yang Anda coba lakukan melalui cara lain (yang hanya merupakan argumen yang valid jika Anda memberikan contoh cara yang lebih baik secara objektif dalam melakukan sesuatu, yang pernah saya lihat sangat kecil)
  2. Hanya karena kita bisa bukan berarti kita harus. Hebat: tetapi orang-orang memposting contoh spesifik tentang bagaimana itu akan berguna/menguntungkan. Saya tidak melihat bagaimana sakitnya membiarkannya.

Kasus penggunaan sederhana lainnya: (cara ideal, yang tidak berfungsi)

import {map} from 'lodash';

export abstract class BaseListModel {
  abstract static get instance_type();

  items: any[];

  constructor(items?: any[]) {
    this.items = map(items, (item) => { return new this.constructor.instance_type(item) });
  }

  get length() { return this.items.length; }
}

export class QuestionList extends BaseListModel {
  static get instance_type() { return Question }
}

Sebagai gantinya, instance daftar akhirnya mengekspos tipe instance secara langsung, yang tidak relevan dengan instance daftar itu sendiri dan harus diakses melalui konstruktor. Terasa kotor. Akan terasa jauh lebih kotor jika kita berbicara tentang model rekaman, di mana menentukan satu set nilai default melalui mekanisme yang sama, dll.

Saya sangat bersemangat untuk menggunakan kelas abstrak nyata dalam bahasa setelah berada di Ruby/javascript begitu lama, hanya untuk akhirnya kecewa dengan batasan implementasi - Contoh di atas hanyalah contoh pertama saya untuk menemukannya, meskipun saya dapat memikirkan banyak kasus penggunaan lain di mana itu akan berguna. Terutama, hanya sebagai sarana untuk membuat DSL/konfigurasi sederhana sebagai bagian dari antarmuka statis, dengan memastikan bahwa kelas menentukan objek nilai default atau apa pun itu. - Dan Anda mungkin berpikir, untuk itulah antarmuka. Tetapi untuk sesuatu yang sederhana seperti ini, itu tidak benar-benar masuk akal, dan hanya berakhir pada hal-hal yang lebih rumit dari yang seharusnya (subclass perlu memperluas kelas abstrak dan mengimplementasikan beberapa antarmuka, membuat penamaan menjadi lebih rumit, dll).

Saya memiliki persyaratan serupa untuk proyek saya dua kali. Keduanya terkait untuk menjamin bahwa semua subclass menyediakan implementasi konkret dari serangkaian metode statis. Skenario saya dijelaskan di bawah ini:

class Action {
  constructor(public type='') {}
}

class AddAppleAction extends Action {
  static create(apple: Apple) {
    return new this(apple);
  }
  constructor(public apple: Apple) {
    super('add/apple');
  }
}

class AddPearAction extends Action {
  static create(pear: Pear) {
    return new this(pear);
  }

  constructor(public pear: Pear) {
    super('add/pear');
  }
}

const ActionCreators = {
  addApple: AddAppleAction
  addPear: AddPearAction
};

const getActionCreators = <T extends Action>(map: { [key: string]: T }) => {
  return Object.entries(map).reduce((creators, [key, ActionClass]) => ({
    ...creators,
    // To have this function run properly,
    // I need to guarantee that each ActionClass (subclass of Action) has a static create method defined.
    // This is where I want to use abstract class or interface to enforce this logic.
    // I have some work around to achieve this by using interface and class decorator, but it feels tricky and buggy. A native support for abstract class method would be really helpful here.
    [key]: ActionClass.create.bind(ActionClass)
  }), {});
};

Semoga ini bisa menjelaskan persyaratan saya. Terima kasih.

Bagi siapa pun yang mencari solusi , Anda dapat menggunakan dekorator ini:

class myClass {
    public classProp: string;
}

interface myConstructor {
    new(): myClass;

    public readonly staticProp: string;
}

function StaticImplements <T>() {
    return (constructor: T) => { };
}

<strong i="7">@StaticImplements</strong> <myConstructor>()
class myClass implements myClass {}
const getActionCreators = <T extends Action>(map: { [key: string]: {new () => T, create() : T}}) => {

karena kita memanggil melalui parameter type, kelas dasar yang sebenarnya dengan hipotetisnya abstract static metode pabrik tidak akan pernah dilibatkan. Tipe dari subclass terkait secara struktural ketika parameter tipe dipakai.

Apa yang tidak saya lihat dalam diskusi ini adalah kasus di mana implementasi dipanggil melalui tipe kelas dasar sebagai lawan dari beberapa tipe yang disintesis.

Juga relevan untuk mempertimbangkan bahwa, tidak seperti dalam bahasa seperti C# di mana anggota abstract sebenarnya _diganti_ oleh implementasinya di kelas turunan, implementasi anggota JavaScript, contoh atau lainnya, tidak pernah menimpa angka yang diwarisi, melainkan _membayangi_ mereka.

5 sen saya di sini, dari sudut pandang pengguna adalah:

Untuk kasus antarmuka, pengubah _static_ harus diizinkan

Antarmuka mendefinisikan kontrak, yang harus dipenuhi dengan mengimplementasikan kelas. Ini berarti bahwa antarmuka adalah abstraksi pada tingkat yang lebih tinggi daripada kelas.

Saat ini apa yang dapat saya lihat adalah bahwa kelas dapat lebih ekspresif daripada antarmuka, dengan cara yang kita dapat memiliki metode statis di kelas tetapi kita tidak dapat menegakkan itu, dari definisi kontrak itu sendiri.

Menurut saya itu terasa salah dan menghambat.

Apakah ada latar belakang teknis yang membuat fitur bahasa ini sulit atau tidak mungkin diterapkan?

Bersulang

Antarmuka mendefinisikan kontrak, yang harus dipenuhi dengan mengimplementasikan kelas. Ini berarti bahwa antarmuka adalah abstraksi pada tingkat yang lebih tinggi daripada kelas.

Kelas memiliki dua antarmuka, dua kontrak implementasi, dan tidak ada jalan keluar dari itu.
Ada alasan mengapa bahasa seperti C# tidak memiliki anggota statis untuk antarmuka juga. Logikanya, antarmuka adalah permukaan publik dari suatu objek. Antarmuka menggambarkan permukaan publik suatu objek. Itu berarti tidak mengandung apa pun yang tidak ada. Pada instance kelas, metode statis tidak ada pada instance. Mereka hanya ada pada fungsi kelas/konstruktor, oleh karena itu mereka hanya boleh dijelaskan di antarmuka itu.

Pada instance kelas, metode statis tidak ada pada instance.

Dapatkah Anda menguraikan itu? Ini bukan C#

Mereka hanya ada pada fungsi kelas/konstruktor, oleh karena itu mereka hanya boleh dijelaskan di antarmuka itu.

Bahwa kami mendapatkannya dan itulah yang ingin kami ubah.

Antarmuka mendefinisikan kontrak, yang harus dipenuhi dengan mengimplementasikan kelas. Ini berarti bahwa antarmuka adalah abstraksi pada tingkat yang lebih tinggi daripada kelas.

Saya sepenuhnya setuju akan hal itu. Lihat contoh antarmuka Json

Hai @kitsonk , bisakah Anda menjelaskan lebih lanjut tentang:

Kelas memiliki dua antarmuka, dua kontrak implementasi, dan tidak ada jalan keluar dari itu.

Saya tidak mengerti bagian itu.

Logikanya, antarmuka adalah permukaan publik dari suatu objek. Antarmuka menggambarkan permukaan publik suatu objek. Itu berarti tidak mengandung apa pun yang tidak ada.

Saya setuju. Saya tidak melihat adanya kontradiksi dengan apa yang saya katakan. Saya bahkan mengatakan lebih banyak. Saya mengatakan antarmuka adalah kontrak untuk kelas yang harus dipenuhi.

Pada instance kelas, metode statis tidak ada pada instance. Mereka hanya ada pada fungsi kelas/konstruktor, oleh karena itu mereka hanya boleh dijelaskan di antarmuka itu.

Tidak yakin saya memahami ini dengan benar, jelas apa yang dikatakannya, tetapi tidak mengapa Anda mengatakannya. Saya dapat menjelaskan mengapa saya pikir pernyataan saya masih valid. Saya meneruskan antarmuka sebagai parameter sepanjang waktu ke metode saya, ini berarti saya memiliki akses ke metode antarmuka, harap dicatat bahwa saya tidak menggunakan antarmuka di sini sebagai cara untuk mendefinisikan struktur data tetapi untuk mendefinisikan objek konkret yang dibuat/dihidrasi di tempat lain . Jadi ketika saya memiliki:

fetchData(account: SalesRepresentativeInterface): Observable<Array<AccountModel>> {
    // Method Body
}

Di badan metode itu, saya memang bisa menggunakan metode account . Apa yang ingin saya lakukan adalah dapat menggunakan metode statis dari SalesRepresentativeInterface yang telah diberlakukan untuk diimplementasikan di kelas apa pun yang saya terima di account . Mungkin saya memiliki ide yang sangat sederhana atau sepenuhnya salah tentang cara menggunakan fitur tersebut.

Saya pikir mengizinkan pengubah static akan memungkinkan saya untuk melakukan sesuatu seperti: SalesRepresentativeInterface.staticMethodCall()

Apakah aku salah ?

Bersulang

@davidmpaz : sintaks Anda kurang tepat, tapi tutup. Berikut contoh pola penggunaan:

interface JSONSerializable {
  static fromJSON(json: any): JSONSerializable;
  toJSON(): any;
}

function makeInstance<T extends JSONSerializable>(cls: typeof T): T {
  return cls.fromJSON({});
}

class ImplementsJSONSerializable implements JSONSerializable {
  constructor(private json: any) {
  }
  static fromJSON(json: any): ImplementsJSONSerializable {
    return new ImplementsJSONSerializable(json);
  }
  toJSON(): any {
    return this.json;
  }
}

// returns an instance of ImplementsJSONSerializable
makeInstance(ImplementsJSONSerializable);

Sayangnya, agar ini berfungsi, kita memerlukan dua fitur dari TypeScript: (1) metode statis dalam antarmuka dan metode statis abstrak; (2) kemampuan untuk menggunakan typeof sebagai petunjuk tipe dengan kelas generik.

@davidmpaz

Saya tidak mengerti bagian itu.

Kelas memiliki dua antarmuka. Fungsi konstruktor dan prototipe instance. Kata kunci class pada dasarnya adalah gula untuk itu.

interface Foo {
  bar(): void;
}

interface FooConstructor {
  new (): Foo;
  prototype: Foo;
  baz(): void;
}

declare const Foo: FooConstructor;

const foo = new Foo();
foo.bar();  // instance method
Foo.baz(); // static method

@jimmykane

Dapatkah Anda menguraikan itu? Ini bukan C#

class Foo {
    bar() {}
    static baz() {}
}

const foo = new Foo();

foo.bar();
Foo.baz();

Tidak ada .baz() pada instance Foo . .baz() hanya ada di konstruktor.

Dari perspektif tipe, Anda dapat mereferensikan/mengekstrak dua antarmuka ini. Foo merujuk ke antarmuka instance publik dan typeof Foo merujuk ke antarmuka konstruktor publik (yang akan menyertakan metode statis).

Untuk menindaklanjuti penjelasan @kitsonk , berikut adalah contoh di atas yang ditulis ulang untuk mencapai apa yang Anda inginkan:

interface JSONSerializable <T>{
    fromJSON(json: any): T;
}

function makeInstance<T>(cls: JSONSerializable<T>): T {
    return cls.fromJSON({});
}

class ImplementsJSONSerializable {
    constructor (private json: any) {
    }
    static fromJSON(json: any): ImplementsJSONSerializable {
        return new ImplementsJSONSerializable(json);
    }
    toJSON(): any {
        return this.json;
    }
}

// returns an instance of ImplementsJSONSerializable
makeInstance(ImplementsJSONSerializable); 

Perhatikan di sini bahwa:

  • Klausa implements sebenarnya tidak diperlukan, setiap kali Anda menggunakan kelas, pemeriksaan struktural dilakukan dan kelas akan divalidasi agar sesuai dengan antarmuka yang dibutuhkan.
  • anda tidak perlu typeof untuk mendapatkan tipe konstruktor, pastikan T Anda adalah yang ingin Anda tangkap

@mhegazy : Anda harus menghapus panggilan toJSON saya di antarmuka JSONSerializable. Meskipun fungsi contoh sederhana saya makeInstance hanya memanggil fromJSON , sangat umum untuk ingin membuat instance objek, lalu menggunakannya. Anda telah menghapus kemampuan saya untuk melakukan panggilan pada apa yang dikembalikan oleh makeInstance , karena saya tidak benar-benar tahu apa T di dalam makeInstance (sangat relevan jika makeInstance adalah metode kelas yang digunakan secara internal untuk membuat instance, lalu menggunakannya). Tentu saja saya bisa melakukan ini:

interface JSONSerializer {
  toJSON(): any;
}

interface JSONSerializable <T extends JSONSerializer> {
  fromJSON(json: any): T;
}

function makeInstance<T extends JSONSerializer>(cls: JSONSerializable<T>): T {
  return cls.fromJSON({});
}

class ImplementsJSONSerializer implements JSONSerializer {
  constructor (private json: any) {
  }
  static fromJSON(json: any): ImplementsJSONSerializer {
    return new ImplementsJSONSerializer(json);
  }
  toJSON(): any {
    return this.json;
  }
}

// returns an instance of ImplementsJSONSerializable
makeInstance(ImplementsJSONSerializer);

Dan sekarang saya tahu bahwa T saya akan memiliki semua metode yang tersedia di JSONSerializer . Tapi ini terlalu bertele-tele dan sulit untuk dipikirkan (tunggu, Anda melewati ImplementsJSONSerializer , tapi ini bukan JSONSerializable , bukan? Tunggu, Anda mengetik bebek??) . Versi yang lebih mudah dipahami adalah:

interface JSONSerializer {
  toJSON(): any;
}

interface JSONSerializable <T extends JSONSerializer> {
  fromJSON(json: any): T;
}

function makeInstance<T extends JSONSerializer>(cls: JSONSerializable<T>): T {
  return cls.fromJSON({});
}

class ImplementsJSONSerializer implements JSONSerializer {
  constructor (private json: any) {
  }
  toJSON(): any {
    return this.json;
  }
}

class ImplementsJSONSerializable implements JSONSerializable<ImplementsJSONSerializer> {
  fromJSON(json: any): ImplementsJSONSerializer {
    return new ImplementsJSONSerializer(json);
  }

}

// returns an instance of ImplementsJSONSerializable
makeInstance(new ImplementsJSONSerializable());

Tapi seperti yang saya tunjukkan di komentar sebelumnya, kita sekarang harus membuat kelas pabrik untuk setiap kelas yang ingin kita buat, yang bahkan lebih verbose daripada contoh mengetik bebek. Itu tentu saja merupakan pola yang bisa diterapkan yang dilakukan di Jawa sepanjang waktu (dan saya menganggap C# juga). Tapi itu tidak perlu verbose dan duplikasi. Semua boilerplate itu hilang jika metode statis diizinkan di antarmuka dan metode statis abstrak diizinkan di kelas abstrak.

tidak perlu kelas tambahan. kelas dapat memiliki static anggota.. Anda hanya tidak memerlukan klausa implements . dalam sistem tipe nominal Anda benar-benar membutuhkannya untuk menegaskan hubungan "adalah" di kelas Anda. dalam sistem tipe struktural, Anda tidak benar-benar membutuhkannya. hubungan "adalah" diperiksa pada setiap penggunaan, jadi Anda dapat dengan aman menghapus klausa implements dan tidak kehilangan keamanan.

jadi dengan kata lain, Anda dapat memiliki satu kelas yang memiliki fromJSON statis dan yang instance-nya memiliki toJSON :

interface JSONSerializer {
    toJSON(): any;
}

interface JSONSerializable<T extends JSONSerializer> {
    fromJSON(json: any): T;
}

function makeInstance<T extends JSONSerializer>(cls: JSONSerializable<T>): T {
    return cls.fromJSON({});
}

class ImplementsJSONSerializer {
    constructor (private json: any) {
    }
    static fromJSON(json: any): ImplementsJSONSerializer {
        return new ImplementsJSONSerializer(json);
    }
    toJSON(): any {
        return this.json;
    }
}


// returns an instance of ImplementsJSONSerializable
makeInstance(ImplementsJSONSerializer);

@mhegazy Saya sudah menunjukkan itu sebagai opsi yang layak. Lihat contoh 'mengetik bebek' pertama saya. Saya berpendapat bahwa itu tidak perlu bertele-tele dan sulit untuk dipikirkan. Saya kemudian menegaskan bahwa versi yang lebih mudah untuk dipikirkan (kelas pabrik) bahkan lebih bertele-tele.

Tidak ada yang tidak setuju bahwa mungkin untuk mengatasi kurangnya metode statis di antarmuka. Faktanya, saya berpendapat ada solusi yang lebih elegan dengan menggunakan gaya fungsional di atas kelas pabrik, meskipun itu adalah preferensi pribadi saya.

Tetapi, menurut saya, alasan paling bersih dan termudah untuk menerapkan pola yang sangat umum ini adalah menggunakan metode statis abstrak. Kami yang telah menyukai fitur ini dalam bahasa lain (Python, PHP) rindu memilikinya di TypeScript. Terima kasih atas waktunya.

Saya kemudian menegaskan bahwa versi yang lebih mudah untuk dipikirkan (kelas pabrik)

tidak yakin saya setuju bahwa ini lebih mudah untuk dipikirkan.

Tetapi, menurut saya, alasan paling bersih dan termudah untuk menerapkan pola yang sangat umum ini adalah menggunakan metode statis abstrak. Kami yang telah menyukai fitur ini dalam bahasa lain (Python, PHP) rindu memilikinya di TypeScript.

Dan masalah ini melacak untuk menambahkan ini. Saya hanya ingin memastikan pengunjung mendatang ke utas ini memahami bahwa ini dapat dilakukan hari ini tanpa klausa implements .

@kitsonk benar saya melewatkan itu.

Teman-teman, jadi saya suka LocationModule, yang seharusnya memiliki metode untuk mendapatkan opsi untuk disimpan dalam database, dari mana ia harus membuat ulang dirinya sendiri.
Setiap LocationModule memiliki pilihan sendiri dengan jenis untuk rekreasi.

Jadi sekarang saya harus membuat pabrik dengan obat generik untuk rekreasi dan bahkan saat itu saya tidak memiliki pemeriksaan waktu kompilasi. Anda tahu, mengalahkan tujuan di sini.

Pada awalnya saya seperti "yah tidak statis untuk antarmuka jadi mari kita tetap menggunakan kelas abstrak TETAPI ini akan menjadi kotor, karena sekarang saya harus mengubah di mana-mana dalam jenis kode saya dari antarmuka ke kelas abstrak sehingga pemeriksa akan mengenali metode yang saya cari .

Di Sini:

export interface LocationModuleInterface {
  readonly name: string;

  getRecreationOptions(): ModuleRecreationOptions;
}

export abstract class AbstractLocationModule implements LocationModuleInterface {
  abstract readonly name: string;

  abstract getRecreationOptions(): ModuleRecreationOptions;

  abstract static recreateFromOptions(options: ModuleRecreationOptions): AbstractLocationModule;
}

Dan kemudian saya menemukan dengan baik, statis tidak bisa dengan abstrak. Dan itu tidak bisa di antarmuka.

Kawan, serius, bukankah kita melindungi tidak menerapkan ini hanya karena tidak menerapkan ini?

Saya sangat menyukai TypeScript. Seperti orang gila maksudku. Tapi selalu ada tapi.

Alih-alih memaksakan penerapan metode statis, saya harus memeriksa ulang seperti yang saya lakukan dengan semua yang ada di JavaScript lama.

Arsitektur sudah penuh dengan pola, keputusan keren dll, tetapi beberapa di antaranya hanya untuk mengatasi masalah dengan menggunakan hal-hal sederhana seperti metode antarmuka statis.

Pabrik saya harus memeriksa keberadaan metode statis runtime. Bagaimana itu?

Atau bahkan lebih baik:

export interface LocationModuleInterface {
  readonly name: string;

  getRecreationOptions(): ModuleRecreationOptions;
  dontForgetToHaveStaticMethodForRecreation();
}

Jika Anda menggunakan objek kelas secara polimorfik, yang merupakan satu-satunya alasan untuk mengimplementasikan antarmuka, tidak masalah jika antarmuka yang menentukan persyaratan untuk sisi statis kelas direferensikan secara sintaksis oleh kelas itu sendiri karena akan diperiksa di situs penggunaan dan mengeluarkan kesalahan waktu desain jika antarmuka yang diperlukan tidak diimplementasikan.

@malina-kirn

Lihat contoh 'mengetik bebek' pertama saya.

Masalah ini tidak ada hubungannya dengan apakah pengetikan bebek digunakan atau tidak. TypeScript adalah tipe bebek.

@aluanhaddad Implikasi Anda tidak benar. Saya dapat memahami maksud Anda bahwa fungsi utama implements ISomeInterface adalah untuk menggunakan objek kelas secara polimorfik. Namun BISA menjadi masalah apakah kelas mereferensikan antarmuka yang menggambarkan bentuk statis objek kelas.

Anda menyiratkan bahwa inferensi tipe implisit dan kesalahan situs penggunaan mencakup semua kasus penggunaan.
Pertimbangkan contoh ini:

interface IComponent<TProps> {
    render(): JSX.Element;
}

type ComponentStaticDisplayName = 'One' | 'Two' | 'Three' | 'Four';
interface IComponentStatic<TProps> {
    defaultProps?: Partial<TProps>;
    displayName: ComponentStaticDisplayName;
    hasBeenValidated: 'Yes' | 'No';
    isFlammable: 'Yes' | 'No';
    isRadioactive: 'Yes' | 'No';
    willGoBoom: 'Yes' | 'No';
}

interface IComponentClass<TProps> extends IComponentStatic<TProps> {
    new (): IComponent<TProps> & IComponentStatic<TProps>;
}

function validateComponentStaticAtRuntime<TProps>(ComponentStatic: IComponentStatic<TProps>, values: any): void {
    if(ComponentStatic.isFlammable === 'Yes') {
        // something
    } else if(ComponentStatic.isFlammable === 'No') {
        // something else
    }
}

// This works, we've explicitly described the object using an interface
const componentStaticInstance: IComponentStatic<any> = {
    displayName: 'One',
    hasBeenValidated: 'No',
    isFlammable: 'Yes',
    isRadioactive: 'No',
    willGoBoom: 'Yes'
};

// Also works
validateComponentStaticAtRuntime(componentStaticInstance, {});

class ExampleComponent1 implements IComponent<any> {
    public render(): JSX.Element { return null; }

    public static displayName = 'One'; // inferred as type string
    public static hasBeenValidated = 'No'; // ditto ...
    public static isFlammable = 'Yes';
    public static isRadioactive = 'No';
    public static willGoBoom = 'Yes';
}

// Error: " ... not assignable to type IComponentStatic<..> ... "
validateComponentStaticAtRuntime(ExampleComponent1, {});

class ExampleComponent2 implements IComponent<any> {
    public render(): JSX.Element { return null; }

    public static displayName = 'One';
    public static hasBeenValidated = 'No';
    public static isFlammable = 'Yes';
    public static isRadioactive = 'No';
    public static willGoBoom = 'Yes';
}

// Error: " ... not assignable to type IComponentStatic<..> ... "
validateComponentStaticAtRuntime(ExampleComponent2, {});

Dalam contoh di atas deklarasi tipe eksplisit diperlukan; tidak menggambarkan bentuk objek kelas (sisi statis) menghasilkan kesalahan waktu desain di lokasi penggunaan meskipun nilai sebenarnya sesuai dengan bentuk yang diharapkan.

Selain itu, kurangnya cara untuk mereferensikan antarmuka yang menjelaskan sisi statis, membuat kita hanya memiliki satu-satunya pilihan untuk secara eksplisit mendeklarasikan tipe pada setiap anggota individu; dan kemudian mengulanginya di setiap kelas.

Berdasarkan posting saya sebelumnya, saya merasa bahwa pengubah static di antarmuka adalah solusi yang sangat bagus untuk beberapa kasus penggunaan yang perlu diselesaikan. Saya minta maaf atas seberapa besar komentar ini telah berkembang tetapi ada banyak hal yang ingin saya ilustrasikan.

1) Ada kalanya kita harus bisa menggambarkan secara eksplisit bentuk sisi statis dari suatu objek kelas, seperti pada contoh saya di atas.
2) Ada kalanya pemeriksaan bentuk di situs definisi sangat diinginkan, dan saat-saat seperti dalam contoh @OliverJAsh di mana situs penggunaan dalam kode eksternal yang tidak akan diperiksa dan Anda perlu memeriksa bentuk di situs definisi .

Mengenai nomor 2, banyak posting yang saya baca menyarankan pemeriksaan bentuk karena situs penggunaan lebih dari cukup. Tetapi dalam kasus di mana situs penggunaan berada di modul galaksi jauh, jauh.... atau ketika situs penggunaan berada di lokasi eksternal yang tidak akan diperiksa, ini jelas bukan masalahnya.

Posting lain menyarankan #workarounds untuk membentuk cek di situs definisi. Meskipun solusi yang ditakuti ini akan memungkinkan Anda untuk memeriksa bentuk di situs definisi (dalam beberapa kasus), ada masalah:

  • Mereka tidak menyelesaikan nomor 1 di atas, Anda masih harus secara eksplisit mendeklarasikan tipe pada setiap anggota di setiap kelas.
  • Dalam banyak kasus mereka tidak memiliki kejelasan, keanggunan, dan keterbacaan. Mereka tidak selalu mudah digunakan, tidak intuitif, dan dapat memakan waktu cukup lama untuk mengetahui bahwa mereka mungkin.
  • Mereka tidak bekerja dalam semua kasus dan menemukan #workaroundsToMakeTheWorkaroundsWork tidak menyenangkan... atau produktif... tetapi kebanyakan tidak menyenangkan.

Berikut adalah contoh solusi yang saya lihat akhir-akhir ini untuk memaksa pemeriksaan bentuk di situs definisi...

// (at least) two separate interfaces
interface ISomeComponent { ... }
interface ISomeComponentClass { ... }

// confusing workaround with extra work to use, not very intuitive, etc
export const SomeComponent: ISomeComponentClass =
class SomeComponent extends Component<IProps, IState> implements ISomeComponent {
...
}

Sekarang coba gunakan solusi itu dengan kelas abstrak yang dilemparkan

interface ISomeComponent { ... }
// need to separate the static type from the class type, because the abstract class won't satisfy
// the shape check for new(): ... 
interface ISomeComponentStatic { ... }
interface ISomeComponentClass { 
    new (): ISomeComponent & ISomeComponentStatic;
}

// I AM ERROR.    ... cannot find name abstract
export const SomeComponentBase: ISomeComponentStatic =
abstract class SomeComponentBase extends Component<IProps, IState> implements ISomeComponent {
...
}

export const SomeComponent: ISomeComponentClass =
class extends SomeComponentBase { ... }

Kami akan saya kira kami akan mengatasinya juga ... menghela nafas

...

abstract class SomeComponentBaseClass extends Component<IProps, IState> implements ISomeComponent { 
   ... 
}

// Not as nice since we're now below the actual definition site and it's a little more verbose
// but hey at least were in the same module and we can check the shape if the use site is in
// external code...
// We now have to decide if we'll use this work around for all classes or only the abstract ones
// and instead mix and match workaround syntax
export const SomeComponentBase: ISomeComponentStatic = SomeComponentBaseClass;

Tapi Tunggu, Masih Ada Lagi! Mari kita lihat apa yang terjadi jika kita menggunakan obat generik....

interface IComponent<TProps extends A> { ... }
interface IComponentStatic<TProps extends A> { ... }

interface IComponentClass<TProps extends A, TOptions extends B> extends IComponentStatic<TProps> {
    new (options: TOptions): IComponent<TProps> & IComponentStatic<TProps>;
}

abstract class SomeComponentBaseClass<TProps extends A, TOptions extends B> extends Component<TProps, IState> implements IComponent<TProps> {
...
}

// Ruh Roh Raggy: "Generic type .... requires 2 type argument(s) ..."
export const SomeComponentBase: IComponentStatic = SomeComponentBaseClass;


// "....  requires 1 type argument(s) ... "    OH NO, We need a different workaround
export const SomeComponent: IComponentStatic =
class extends SomeComponentBase<TProps extends A, ISomeComponentOptions> {
...
}

Pada titik ini jika Anda memiliki situs penggunaan dalam kode yang akan diperiksa oleh TypeScript, Anda mungkin harus menggigit peluru dan mengandalkan itu bahkan jika itu sangat jauh.

Jika Anda menggunakan situs eksternal, yakinkan komunitas/pengelola untuk menerima pengubah static untuk anggota antarmuka. Dan sementara itu Anda akan membutuhkan solusi lain. Itu tidak ada di situs definisi tetapi saya yakin Anda dapat menggunakan versi modifikasi dari solusi yang dijelaskan di #8328 untuk mencapai ini. Lihat solusinya di komentar dari @mhegazy pada 16 Mei 2016 milik @RyanCavanaugh

Maafkan saya jika saya kehilangan poin-poin penting. Pemahaman saya tentang keraguan untuk mendukung static untuk anggota antarmuka terutama adalah ketidaksukaan menggunakan antarmuka yang sama + mengimplementasikan kata kunci untuk menggambarkan bentuk konstruktor dan bentuk instance.

Izinkan saya mengawali analogi berikut dengan mengatakan bahwa saya menyukai apa yang ditawarkan TypeScript dan sangat menghargai mereka yang telah mengembangkannya dan komunitas yang memberikan banyak pemikiran dan upaya untuk menangani sejumlah besar permintaan fitur; melakukan yang terbaik untuk menerapkan fitur yang sesuai.

Tetapi, keragu-raguan dalam kasus ini tampak bagi saya sebagai keinginan untuk mempertahankan 'kemurnian konseptual' dengan mengorbankan kegunaan. Sekali lagi maaf jika analogi ini jauh dari dasar:

Ini mengingatkan untuk beralih dari Java ke C# . Ketika saya pertama kali melihat kode C# melakukan ini
C# var something = "something"; if(something == "something") { ... }
bel alarm mulai berbunyi di kepalaku, dunia akan berakhir, mereka harus menggunakan "something".Equals(something) , dll

== adalah untuk referensi sama! Anda memiliki .Equals(...) terpisah untuk perbandingan string...
dan literal string harus didahulukan sehingga Anda tidak mendapatkan referensi nol yang memanggil .Equals(...) pada variabel nol...
dan... dan... hiperventilasi

Dan kemudian setelah beberapa minggu menggunakan C# saya menyadari betapa lebih mudahnya menggunakannya karena perilaku itu. Meskipun itu berarti melepaskan perbedaan yang jelas antara keduanya, itu membuat peningkatan dramatis dalam kegunaan sehingga itu sepadan.

Itulah yang saya rasakan tentang pengubah static untuk anggota antarmuka. Ini akan memungkinkan kita untuk terus menggambarkan bentuk instance seperti yang sudah kita lakukan, memungkinkan kita untuk menggambarkan bentuk fungsi konstruktor yang hanya dapat kita lakukan dengan solusi yang buruk kasus per kasus, dan memungkinkan kita untuk melakukan keduanya dengan relatif bersih dan mudah cara. Peningkatan besar dalam kegunaan, IMO.

Saya akan mempertimbangkan abstract static di komentar berikutnya....

Antarmuka selalu membutuhkan spesifikasi ulang tipe anggota pada kelas implementasi. (inferensi tanda tangan anggota di kelas implementasi _eksplisit_ adalah fitur terpisah, yang belum didukung).

Alasan Anda mendapatkan kesalahan tidak terkait dengan proposal ini. Untuk menyiasatinya, Anda perlu menggunakan pengubah readonly pada implementasi anggota kelas, statis atau lainnya, yang harus bertipe literal; jika tidak, string akan disimpulkan.

Sekali lagi kata kunci implements hanyalah spesifikasi maksud, tidak mempengaruhi kompatibilitas struktural tipe, atau memperkenalkan pengetikan nominal. Itu tidak berarti bahwa itu tidak berguna tetapi itu tidak mengubah jenisnya.

Jadi abstract static ... Tidak Layak. Terlalu banyak masalah.

Anda akan memerlukan sintaks baru dan kompleks untuk mendeklarasikan apa yang sudah akan diperiksa di situs penggunaan secara implisit (jika situs penggunaan akan diperiksa oleh kompiler TypeScript).

Jika situs yang digunakan bersifat eksternal, maka yang Anda perlukan adalah static anggota antarmuka... Maaf selesai menyambungkan.... dan selamat malam!

@aluanhaddad Benar, tetapi bahkan jika inferensi tanda tangan anggota dalam mengimplementasikan kelas secara eksplisit adalah fitur yang didukung, kami tidak memiliki cara yang bersih untuk mendeklarasikan tanda tangan anggota untuk sisi statis kelas.

Poin yang saya coba ungkapkan adalah bahwa kami kekurangan cara untuk explicitly mendeklarasikan struktur yang diharapkan dari sisi statis kelas dan ada kasus di mana itu penting (atau akan penting untuk mendukung fitur)

Terutama saya ingin membantah bagian komentar Anda ini "tidak masalah jika antarmuka yang menentukan persyaratan untuk sisi statis kelas direferensikan secara sintaksis".

Saya mencoba menggunakan inferensi tipe sebagai contoh mengapa itu penting dengan dukungan di masa mendatang.
Dengan mereferensikan antarmuka secara sintaksis di sini let something: ISomethingStatic = { isFlammable: 'Ys', isFlammable2: 'No' ... isFlammable100: 'No' } kita tidak perlu mendeklarasikan tipe secara eksplisit sebagai 'Ya' | 'Tidak' 100 kali terpisah.

Untuk mencapai inferensi tipe yang sama untuk sisi statis suatu kelas (di masa mendatang) kita akan memerlukan beberapa cara untuk mereferensikan antarmuka secara sintaksis.

Setelah membaca komentar terakhir Anda, saya pikir itu bukan contoh yang jelas seperti yang saya harapkan. Mungkin contoh yang lebih baik adalah ketika situs penggunaan untuk sisi statis kelas berada dalam kode eksternal yang tidak akan diperiksa oleh kompiler TypeScript. Dalam kasus seperti itu, kami saat ini harus mengandalkan solusi yang pada dasarnya membuat situs penggunaan buatan, memberikan kegunaan/keterbacaan yang buruk, dan tidak berfungsi dalam banyak kasus.

Dengan mengorbankan beberapa verbositas, Anda dapat mencapai tingkat keamanan tipe yang layak:

type HasType<T, Q extends T> = Q;

interface IStatic<X, Y> {
    doWork: (input: X) => Y
}

type A_implments_IStatic<X, Y> = HasType<IStatic<X, Y>, typeof A>    // OK
type A_implments_IStatic2<X> = HasType<IStatic<X, number>, typeof A> // OK
type A_implments_IStatic3<X> = HasType<IStatic<number, X>, typeof A> // OK
class A<X, Y> {
    static doWork<T, U>(_input: T): U {
        return null!;
    }
}

type B_implments_IStatic = HasType<IStatic<number, string>, typeof B> // Error as expected
class B {
    static doWork(n: number) {
        return n + n;
    }
}

Setuju ini harus diizinkan. Mengikuti kasus penggunaan sederhana akan mendapat manfaat dari metode statis abstrak:

export abstract class Component {
  public abstract static READ_FROM(buffer: ByteBuffer): Component;
}

// I want to force my DeckComponent class to implement it's own static ReadFrom method
export class DeckComponent extends Component {
  public cardIds: number[];

  constructor(cardIds: number[]) {
    this.cardIds = cardIds;
  }

  public static READ_FROM(buffer: ByteBuffer): DeckComponent {
    const cardIds: number[] = [...];
    return new DeckComponent(cardIds);
  }
}

@RyanCavanaugh Saya rasa belum ada yang menyebutkan ini dengan tepat (walaupun saya bisa melewatkannya dalam pembacaan cepat saya), tetapi sebagai tanggapan terhadap:

Sebuah pertanyaan besar yang kami miliki ketika mempertimbangkan ini: Siapa yang diizinkan untuk memanggil metode statis abstrak? Agaknya Anda tidak dapat memanggil AbstractParentClass.getSomeClassDependentValue secara langsung. Tetapi bisakah Anda memanggil metode pada ekspresi tipe AbstractParentClass? Jika demikian, mengapa itu harus diizinkan? Jika tidak, apa gunanya fitur tersebut?

Ini dapat diatasi dengan menerapkan bagian #3841. Membaca masalah itu, keberatan utama yang diangkat tampaknya adalah bahwa jenis fungsi konstruktor dari kelas turunan seringkali tidak kompatibel dengan fungsi konstruktor dari kelas dasar mereka. Namun kekhawatiran yang sama tampaknya tidak berlaku untuk metode atau bidang statis lainnya karena TypeScript sudah memeriksa apakah statika yang diganti kompatibel dengan tipenya di kelas dasar.

Jadi yang saya usulkan adalah memberikan T.constructor tipe Function & {{ statics on T }} . Itu akan memungkinkan kelas abstrak yang mendeklarasikan bidang abstract static foo untuk mengaksesnya dengan aman melalui this.constructor.foo tanpa menyebabkan masalah dengan ketidakcocokan konstruktor.

Dan bahkan jika secara otomatis menambahkan statika ke T.constructor tidak diterapkan, kami masih dapat menggunakan properti abstract static di kelas dasar dengan mendeklarasikan "constructor": typeof AbstractParentClass secara manual.

Saya pikir banyak orang berharap contoh @patryk- zielinski93 akan berhasil. Sebagai gantinya, kita harus menggunakan solusi yang berlawanan dengan intuisi, verbose, dan samar. Karena kita sudah memiliki kelas 'syntax sugared' dan anggota statis, mengapa kita tidak dapat memiliki sistem tipe gula seperti itu?

Inilah rasa sakit saya:

abstract class Shape {
    className() {
        return (<typeof Shape>this.constructor).className;
    }
    abstract static readonly className: string; // How to achieve it?
}

class Polygon extends Shape {
    static readonly className = 'Polygon';
}

class Triangle extends Polygon {
    static readonly className = 'Triangle';
}

Mungkin kita bisa/secara paralel memperkenalkan klausa static implements ? misalnya

interface Foo {
    bar(): number;
}

class Baz static implements Foo {
    public static bar() {
        return 4;
    }
}

Ini memiliki keuntungan karena kompatibel dengan mundur dengan mendeklarasikan antarmuka terpisah untuk anggota statis dan instan, yang tampaknya merupakan solusi pilihan saat ini, jika saya membaca utas ini dengan benar. Ini juga merupakan fitur yang agak berbeda yang saya lihat berguna dalam dirinya sendiri, tetapi itu tidak penting.

( statically implements akan lebih baik, tetapi itu memperkenalkan kata kunci baru. Dapat diperdebatkan jika ini sepadan. Ini bisa menjadi kontekstual.)

Tidak peduli seberapa keras saya mencoba memahami argumen dari mereka yang skeptis tentang proposal OP, saya gagal.

Hanya satu orang (https://github.com/Microsoft/TypeScript/issues/14600#issuecomment-379645122) yang menjawab Anda @RyanCavanaugh . Saya menulis ulang kode OP untuk membuatnya sedikit lebih bersih dan juga untuk menggambarkan pertanyaan Anda dan jawaban saya:

abstract class Base {
  abstract static foo() // ref#1
}

class Child1 extends Base {
  static foo() {...} // ref#2
}

class Child2 extends Base {
  static foo() {...}
}

Siapa yang diizinkan untuk memanggil metode statis abstrak? Agaknya Anda tidak dapat memanggil Base.foo secara langsung

Jika maksud Anda ref#1, maka ya, itu tidak boleh dipanggil, karena itu abstrak, dan bahkan tidak memiliki badan.
Anda hanya diperbolehkan menelepon ref#2.

Tetapi bisakah Anda memanggil metode pada ekspresi tipe Basis?
Jika demikian, mengapa itu harus diizinkan?

function bar(baz:Base) { // "expression of type Base"
  baz.foo() // ref#3
}

function bar2(baz: typeof Base) { // expression of type Base.constructor
  baz.foo() // ref#4
}

ref#3 adalah kesalahan. Tidak, Anda tidak dapat memanggil "foo" di sana, karena baz seharusnya merupakan __instance__ dari Child1 atau Child2, dan instance tidak memiliki metode statis

ref#4 adalah (seharusnya) benar. Anda dapat (harus dapat) memanggil metode statis foo di sana, karena baz seharusnya menjadi konstruktor Child1 atau Child2, yang keduanya memperluas Basis dan, oleh karena itu, harus telah mengimplementasikan foo().

bar2(Child1) // ok
bar2(Child2) // ok

Anda dapat membayangkan situasi ini:

bar2(Base) // ok, but ref#4 should be red-highlighted with compile error e.g. "Cannot call abstract  method." 
// Don't know if it's possible to implement in compiler, though. 
// If not, compiler simply should not raise any error and let JS to do it in runtime (Base.foo is not a function). 

Apa kegunaan fitur tersebut?

Lihat ref#4 Penggunaannya adalah ketika kita tidak tahu kelas anak mana yang kita terima sebagai argumen (mungkin Child1 atau Child2), tetapi kita ingin memanggil metode statisnya dan memastikan bahwa metode itu ada.
Saya menulis ulang kerangka kerja node.js, yang disebut AdonisJS. Itu ditulis dalam JS murni, jadi saya mengubahnya menjadi TS. Saya tidak dapat mengubah cara kerja kode, saya hanya menambahkan tipe. Kurangnya fitur ini membuat saya sangat sedih.

ps Dalam komentar ini, untuk alasan kesederhanaan, saya hanya menulis tentang kelas abstrak, dan tidak menyebutkan antarmuka. Tetapi semua yang saya tulis berlaku untuk antarmuka. Anda cukup mengganti kelas abstrak dengan antarmuka, dan semuanya akan benar. Ada kemungkinan, ketika tim TS karena alasan tertentu (tidak tahu mengapa) tidak ingin mengimplementasikan abstract static di abstract class , namun akan baik-baik saja untuk hanya mengimplementasikan static kata di antarmuka, itu akan membuat kita cukup senang, kurasa.

pps Saya mengedit nama kelas dan metode dalam pertanyaan Anda agar sesuai dengan kode saya.

Beberapa pemikiran, berdasarkan argumen utama skeptis lainnya:

"Tidak, ini tidak boleh diterapkan, tetapi Anda sudah dapat melakukannya dengan cara ini *menulis 15 kali lebih banyak baris kode*".

Untuk itulah gula sintaksis dibuat. Mari kita tambahkan sedikit (gula) ke TS. Tapi tidak, lebih baik kita menyiksa otak pengembang yang mencoba menerobos dekorator dan selusin obat generik daripada menambahkan satu kata static sederhana ke antarmuka. Mengapa tidak menganggap static itu sebagai gula lain untuk membuat hidup kita lebih mudah? Cara yang sama seperti ES6 menambahkan class (yang menjadi umum saat ini). Tapi tidak, mari kita menjadi kutu buku dan melakukan hal-hal lama dan "benar".

"Ada dua antarmuka untuk kelas js: untuk konstruktor dan instance".

Ok, mengapa tidak memberi kami cara untuk membuat antarmuka untuk konstruktor? Namun cukup menambahkan kata static itu jauh lebih mudah, dan lebih intuitif.

Dan mengenai ini:

Menunda ini sampai kami mendengar lebih banyak umpan balik tentang itu.

Lebih dari satu tahun telah berlalu, dan banyak umpan balik yang diberikan, untuk berapa lama masalah ini akan ditunda? Adakah yang bisa menjawab, tolong.

Posting ini mungkin tampak agak kasar... Tapi tidak, ini adalah permintaan dari orang yang mencintai TS dan pada saat yang sama benar-benar tidak dapat menemukan satu alasan kuat mengapa kita harus pergi dengan peretasan yang buruk, ketika mengonversi basis kode JS lama kita ke TS. Terlepas dari ini, huuuge terima kasih kepada tim TS. TS luar biasa, itu mengubah hidup saya dan saya menikmati pengkodean dan pekerjaan saya lebih dari sebelumnya ... Tapi masalah ini meracuni pengalaman saya..

Kemungkinan solusi?

export type Constructor<T> = new (...args: any[]) => T;

declare global {
  interface Function extends StaticInterface {
  }
}

export interface StaticInterface {
  builder: (this: Constructor<MyClass>) => MyClass
}

// TODO add decorator that adds "builder" implementation
export class MyClass {
  name = "Ayyy"
}

export class OtherClass {
  id = "Yaaa"
}

MyClass.builder() // ok
OtherClass.builder() // error

Proposal: Anggota statis di antarmuka dan tipe, dan anggota abstrak di kelas abstrak, v2

Gunakan kasus

Jenis yang sesuai dengan Fantasyland

```` naskah yang diketik
interface Applicative extends Apply {static dari (a: A): Applicative ; }

const dari = >(c: C, a: A): new C => c.of(a); ````

Serializable ketik dengan metode deserialize statis

```` naskah yang diketik
antarmuka Serializable {
deserialize statis (s: string): Serializable;

serialize(): string;

}
````

Kontrak pada umumnya

```` naskah yang diketik
kontrak antarmuka {
static create(x: number): Kontrak;
statis baru(x: angka): Kontrak;
}

const factory1 = (c: Kontrak statis): Kontrak => c.create(Math.random());
const factory2 = (C: Kontrak statis): Kontrak => new C(Math.random());
const pabrik3 =(c: C): new C => c.create(Math.random());

const c1 = factory1(ContractImpl); // Contract
const c2 = factory2(ContractImpl); // Contract
const c3 = factory3(ContractImpl); // ContractImpl
````

Sintaksis

Metode dan bidang statis

```` naskah yang diketik
antarmuka Serializable {
deserialize statis (s: string): Serializable;
}

ketik Serializable = {
deserialize statis (s: string): Serializable;
};

kelas abstrak Serializable {
deserialize abstrak statis (s: string): Serializable;
}
````

Tanda tangan konstruktor statis

typescript interface Contract { static new(): Contract; }

Lanjutan: Tanda tangan panggilan statis

Membahas:

Bagaimana tanda tangan panggilan statis dapat diekspresikan?
Jika kita akan mengekspresikannya hanya dengan menambahkan pengubah static sebelum tanda tangan panggilan,
bagaimana kita akan membedakannya dari metode instan dengan nama 'static' ?
Bisakah kita menggunakan solusi yang sama seperti untuk 'new' -metode bernama?
Dalam kasus seperti itu, itu pasti akan menjadi perubahan yang menghancurkan.

Operator tipe static

typescript const deserialize = (Class: static Serializable, s: string): Serializable => Class.deserialize(s);

Operator tipe new

typescript const deserialize = <C extends static Serializable>(Class: C, s: string): new C => Class.deserialize(s);

Slot internal

Slot internal [[Static]]

Antarmuka "Statis" dari suatu jenis akan disimpan di slot internal [[Static]] :

```` naskah yang diketik
// Tipe
antarmuka Serializable {
deserialize statis (s: string): Serializable;
membuat cerita bersambung: string;
}

// akan direpresentasikan secara internal sebagai
// antarmuka Serializable {
// [[Statis]]: {
// [[Instance]]: Serializable; // Lihat di bawah.
// deserialize(s: string): Serializable;
// };
// membuat cerita bersambung(): string;
// }
````

Secara default memiliki tipe never .

Slot internal [[Instance]]

Antarmuka jenis "Instance" akan disimpan
dalam slot internal [[Instance]] dari jenis slot internal [[Static]] .

Secara default memiliki tipe never .

Semantik

Tanpa operator static

Ketika tipe digunakan sebagai tipe nilai,
slot internal [[Static]] akan dibuang:

```` naskah yang diketik
mendeklarasikan const serializable: Serializable;

type T = typeof serializable;
// { serialisasi(): string; }
````

Tetapi tipe itu sendiri tetap mempertahankan slot internal [[Static]] :

typescript type T = Serializable; // { // [[Static]]: { // [[Instance]]: Serializable; // deserialize(s: string): Serializable; // }; // serialize(): string; // }

Dapat dialihkan

Kedua nilai tipe sadar-statis dapat ditetapkan ke yang identik secara struktural
(kecuali [[Static]] , tentu saja) dan sebaliknya.

Operator static

| Asosiatif | Prioritas |
| :-----------: | :--------------------------------: |
| Benar | IDK, tetapi sama dengan new |

Operator tipe static mengembalikan tipe slot internal [[Static]] dari tipe tersebut.
Agak mirip dengan operator tipe typeof ,
tetapi argumennya harus berupa tipe daripada nilai.

typescript type T = static Serializable; // { // [[Instance]]: Serializable; // deserialize(s: string): Serializable; // }

Operator tipe typeof juga membuang slot internal [[Instance]] :

```` naskah yang diketik
mendeklarasikan const SerializableImpl: static Serializable;

tipe T = tipe SerializableImpl;
// { deserialize(s: string): Serializable; }
````

Dapat dialihkan

Kedua nilai dari tipe yang sadar-instance dapat ditetapkan ke yang identik secara struktural
(kecuali slot internal [[Instance]] , tentu saja) dan sebaliknya.

Operator new

| Asosiatif | Prioritas |
| :-----------: | :-----------------------------------: |
| Benar | IDK, tetapi sama dengan static |

Operator new mengembalikan jenis slot internal [[Instance]] dari jenis tersebut.
Ini secara efektif membalikkan operator static :

typescript type T = new static Serializable; // { // [[Static]]: { // [[Instance]]: Serializable; // deserialize(s: string): Serializable; // }; // serialize(): string; // }

extends / implements semantik

Kelas yang mengimplementasikan antarmuka dengan slot internal [[Static]] non-default
HARUS memiliki slot internal [[Static]] yang kompatibel.
Pemeriksaan kompatibilitas harus sama dengan (atau mirip dengan)
pemeriksaan kompatibilitas reguler (contoh).

```` naskah yang diketik
class SerializableImpl mengimplementasikan Serializable {
deserialize statis (s: string): SerializableImpl {
// Logika deserialisasi ada di sini.
}

// ...other static members
// constructor
// ...other members

serialize(): string {
    //
}

}
````

MELAKUKAN

  • [ ] Tentukan, sintaks mana yang harus digunakan untuk tanda tangan panggilan statis.
    Mungkin tanpa merusak perubahan.
  • [ ] Apakah ada kasus khusus dengan tipe kondisional dan operator infer ?
  • [ ] Perubahan pada semantik anggota kelas non-abstrak. _Mungkin rusak._

Saya telah menghabiskan beberapa jam membaca masalah ini dan masalah lain untuk solusi masalah saya, yang akan diselesaikan oleh pengubah statis atau antarmuka untuk kelas. Saya tidak dapat menemukan satu solusi pun yang menyelesaikan masalah saya.

Masalah yang saya miliki adalah saya ingin membuat modifikasi pada kelas yang dihasilkan kode. Misalnya yang ingin saya lakukan adalah:

import {Message} from "../protobuf";

declare module "../protobuf" {
    interface Message {
        static factory: (params: MessageParams) => Message
    }
}

Message.factory = function(params: MessageParams) {
    const message = new Message();
    //... set up properties
    return message;
}

export default Message;

Saya tidak dapat menemukan satu solusi pun yang memungkinkan saya melakukan hal yang setara dengan ini, dengan versi TS saat ini. Apakah saya kehilangan cara untuk menyelesaikan masalah ini saat ini?

Ini terasa relevan untuk diposting di sini sebagai kasus penggunaan yang tampaknya tidak ada solusi dan tentu saja tidak ada solusi langsung.

Jika Anda ingin memeriksa instance dan sisi statis kelas terhadap antarmuka, Anda dapat melakukannya seperti ini:

interface C1Instance {
  // Instance properties ...

  // Prototype properties ...
}
interface C2Instance extends C1Instance {
  // Instance properties ...

  // Prototype properties ...
}

interface C1Constructor {
  new (): C1Instance;

  // Static properties ...
}
interface C2Constructor {
  new (): C2Instance;

  // Static properties ...
}

type C1 = C1Instance;
let C1: C1Constructor = class {};

type C2 = C2Instance;
let C2: C2Constructor = class extends C1 {};

let c1: C1 = new C1();
let c2: C2 = new C2();

Terlalu banyak dari kita yang membuang terlalu banyak waktu kita dan orang lain untuk hal ini. Kenapa tidak apa-apa?!¿i!
Mengapa jawabannya semua solusi besar untuk sesuatu yang harus dapat dibaca, dicerna, dan sesuatu yang 1-liner yang mudah. Tidak mungkin saya ingin seseorang harus mencari tahu apa yang coba dilakukan kode saya dengan beberapa solusi peretasan. Maaf untuk lebih mengacaukan topik ini dengan sesuatu yang tidak terlalu berharga, tapi itu sangat menyakitkan dan membuang-buang waktu saat ini, jadi saya merasa itu berharga bagi saya, yang lain sekarang, dan mereka yang datang kemudian mencari jawaban.

Hal dengan bahasa apa pun, baik alami maupun buatan, harus berkembang secara alami menjadi efisien dan menarik untuk digunakan. Akhirnya orang memutuskan untuk menggunakan pengurangan bahasa, ("oke"=>"ok",,"akan" =>"akan"), menemukan kata-kata konyol baru, seperti "selfie" dan "google", mendefinisikan ulang ejaan dengan l33tspeak dan hal-hal, dan bahkan melarang beberapa kata, dan, meskipun jika Anda ingin menggunakannya atau tidak, semua orang masih agak mengerti apa artinya, dan beberapa dari kita menggunakannya untuk mencapai beberapa tugas apa pun tertentu. Dan untuk semua itu tidak ada alasan yang bagus, tetapi efisiensi orang-orang tertentu dalam tugas-tugas tertentu, itu semua pertanyaan tentang jumlah orang, yang benar-benar memanfaatkan mereka. Volume percakapan ini dengan jelas menunjukkan, bahwa banyak orang dapat menggunakan static abstract ini untuk pertimbangan apa pun yang mereka miliki. Saya datang ke sini untuk alasan yang sama, karena saya ingin menerapkan Serializable jadi saya mencoba semua cara intuitif (untuk saya) untuk melakukannya, dan tidak ada yang berhasil. Percayalah, hal terakhir yang saya cari adalah penjelasan mengapa saya tidak memerlukan fitur ini dan harus mencari yang lain. Setengah tahun, yesus kristus! Saya yakin sudah ada PR di suatu tempat, dengan tes dan lainnya. Tolong buat ini terjadi, dan jika ada cara penggunaan tertentu yang tidak disarankan, kami memiliki tslint untuk itu.

Dimungkinkan untuk mengakses anggota statis kelas anak dari kelas dasar melalui this.constructor.staticMember , jadi anggota statis abstrak masuk akal bagi saya.

class A {
  f() {
    console.log(this.constructor.x)
  }
}

class B extends A {
  static x = "b"
}

const b = new B
b.f() // logs "b"

Kelas A harus dapat menentukan bahwa ia memerlukan anggota x statis, karena ia menggunakannya dalam metode f .

Ada berita ?
Fiturnya sangat dibutuhkan
1) static berfungsi di antarmuka
2) abstract static berfungsi dalam kelas abstrak

meskipun saya benci gagasan memiliki antarmuka statis tetapi untuk semua tujuan praktis berikut ini sudah cukup hari ini :

type MyClass =  (new (text: string) => MyInterface) & { myStaticMethod(): string; }

yang dapat digunakan sebagai:

const MyClass: MyClass = class implements MyInterface {
   constructor(text: string) {}
   static myStaticMethod(): string { return ''; }
}

MEMPERBARUI:

lebih detail tentang ide dan contoh langsung:

// dynamic part
interface MyInterface {
    data: number[];
}
// static part
interface MyStaticInterface {
    myStaticMethod(): string;
}

// type of a class that implements both static and dynamic interfaces
type MyClass = (new (data: number[]) => MyInterface) & MyStaticInterface;

// way to make sure that given class implements both 
// static and dynamic interface
const MyClass: MyClass = class MyClass implements MyInterface {
   constructor(public data: number[]) {}
   static myStaticMethod(): string { return ''; }
}

// works just like a real class
const myInstance = new MyClass([]); // <-- works!
MyClass.myStaticMethod(); // <-- works!

// example of catching errors: bad static part
/*
type 'typeof MyBadClass1' is not assignable to type 'MyClass'.
  Type 'typeof MyBadClass1' is not assignable to type 'MyStaticInterface'.
    Property 'myStaticMethod' is missing in type 'typeof MyBadClass1'.
*/
const MyBadClass1: MyClass = class implements MyInterface {
   constructor(public data: number[]) {}
   static myNewStaticMethod(): string { return ''; }
}

// example of catching errors: bad dynamic part
/*
Type 'typeof MyBadClass2' is not assignable to type 'MyClass'.
  Type 'typeof MyBadClass2' is not assignable to type 'new (data: number[]) => MyInterface'.
    Type 'MyBadClass2' is not assignable to type 'MyInterface'.
      Property 'data' is missing in type 'MyBadClass2'.
*/
const MyBadClass2: MyClass = class implements MyInterface {
   constructor(public values: number[]) {}
   static myStaticMethod(): string { return ''; }
}

@aleksey-bykov ini mungkin bukan kesalahan TypeScript, tapi saya tidak bisa mendapatkan dekorator komponen Angular yang berfungsi ini dan kompiler AoT mereka.

@aleksey-bykov itu pintar tetapi masih tidak berfungsi untuk statis abstrak. Jika Anda memiliki subkelas MyClass , subkelas tersebut tidak diterapkan dengan pemeriksaan tipe. Ini juga lebih buruk jika Anda memiliki obat generik yang terlibat.

// no errors
class Thing extends MyClass {

}

Saya sangat berharap tim TypeScript mempertimbangkan kembali pendirian mereka tentang hal ini, karena membangun perpustakaan pengguna akhir yang memerlukan atribut statis tidak memiliki implementasi yang masuk akal. Kita harus dapat memiliki kontrak yang mengharuskan pelaksana antarmuka/ekstender kelas abstrak memiliki statika.

@bbugh saya mempertanyakan keberadaan masalah yang sedang dibahas di sini, mengapa Anda memerlukan semua masalah ini dengan metode warisan abstrak statis jika hal yang sama dapat dilakukan melalui instance kelas reguler?

class MyAbstractStaticClass {
    abstract static myStaticMethod(): void; // <-- wish we could
}
class MyStaticClass extends MyAbstractStaticClass {
    static myStaticMethod(): void {
         console.log('hi');
    }
}
MyStaticClass.myStaticMethod(); // <-- would be great

vs

class MyAbstractNonStaticClass {
    abstract myAbstractNonStaticMethod(): void;
}
class MyNonStaticClass extends MyAbstractNonStaticClass {
    myNonStaticMethod(): void {
        console.log('hi again');
    }
}
new MyNonStaticClass().myNonStaticMethod(); // <-- works today

@aleksey-bykov Ada banyak alasan. Misalnya ( dari @patryk-zielinski93):

abstract class Serializable {  
    abstract serialize (): Object;  
    abstract static deserialize (Object): Serializable;  
}  

Saya ingin memaksakan implementasi metode deserialize statis di subclass Serializable.
Apakah ada solusi untuk menerapkan perilaku seperti itu?

EDIT: Saya tahu bahwa Anda juga dapat menggunakan deserialize sebagai konstruktor, tetapi kelas hanya dapat memiliki 1 konstruktor, yang membuat metode pabrik diperlukan. Orang-orang menginginkan cara untuk meminta metode pabrik dalam antarmuka, yang sangat masuk akal.

cukup bawa logika keinginan ke kelas yang terpisah, karena tidak ada manfaat memiliki metode deserialisasi statis yang dilampirkan ke kelas yang sedang dideserialisasi

class Deserializer {
     deserializeThis(...): Xxx {}
     deserializeThat(...): Yyy {}
}

Apa masalahnya?

@aleksey-bykov kelas terpisah tidak terlihat cantik

@aleksey-bykov, masalahnya adalah ada lebih dari 1 kelas yang membutuhkan serialisasi, jadi pendekatan Anda memaksa untuk membuat kamus serializable, yang merupakan antipattern uberclass, dan setiap modifikasi pada salah satu dari mereka akan memerlukan pengeditan di uberclass ini, yang membuat dukungan kode menjadi gangguan. Meskipun memiliki antarmuka serial dapat memaksa implementasi ke jenis objek apa pun, dan juga mendukung pewarisan polimorfik, yang merupakan alasan utama orang menginginkannya. Tolong jangan berspekulasi apakah ada manfaat atau tidak, siapa pun harus dapat memiliki pilihan dan memilih apa yang bermanfaat untuk proyeknya sendiri.

@octaharon memiliki kelas yang semata-mata didedikasikan untuk deserialisasi adalah kebalikan dari apa yang Anda katakan, ia memiliki tanggung jawab tunggal karena satu-satunya saat Anda mengubahnya adalah ketika Anda peduli dengan deserialisasi

sebaliknya menambahkan deserialisasi ke kelas itu sendiri seperti yang Anda usulkan memberinya tanggung jawab tambahan dan alasan tambahan untuk diubah

terakhir saya tidak punya masalah metode statis, tetapi saya benar-benar penasaran untuk melihat contoh kasus penggunaan praktis untuk metode statis abstrak dan antarmuka statis

@octaharon memiliki kelas yang semata-mata didedikasikan untuk deserialisasi adalah kebalikan dari apa yang Anda katakan, ia memiliki tanggung jawab tunggal karena satu-satunya saat Anda mengubahnya adalah ketika Anda peduli dengan deserialisasi

kecuali Anda harus mengubah kode di dua tempat, bukan satu, karena serialisasi (de) Anda bergantung pada struktur tipe Anda. Itu tidak termasuk dalam "tanggung jawab tunggal"

sebaliknya menambahkan deserialisasi ke kelas itu sendiri seperti yang Anda usulkan memberinya tanggung jawab tambahan dan alasan tambahan untuk diubah

Saya tidak mengerti apa yang Anda katakan. Kelas yang bertanggung jawab atas (de)serialisasinya sendiri persis seperti yang saya inginkan, dan tolong, jangan beri tahu saya apakah itu benar atau salah.

tanggung jawab tunggal tidak mengatakan apa-apa tentang berapa banyak file yang terlibat, itu hanya mengatakan harus ada satu alasan

apa yang saya katakan adalah bahwa ketika Anda menambahkan kelas baru yang Anda maksudkan untuk sesuatu selain hanya deserialized, jadi sudah memiliki alasan untuk eksis dan tanggung jawab ditugaskan untuk itu; kemudian Anda menambahkan tanggung jawab lain untuk dapat melakukan deserialize itu sendiri, ini memberi kami 2 tanggung jawab, ini melanggar SOLID, jika Anda terus menambahkan lebih banyak barang ke dalamnya seperti membuat, mencetak, menyalin, mentransfer, mengenkripsi, dll, Anda akan mendapatkan kelas dewa

dan maafkan saya memberi tahu Anda hal-hal yang dangkal, tetapi pertanyaan (dan jawaban) tentang manfaat dan kasus penggunaan praktis adalah yang mendorong proposal fitur ini untuk diimplementasikan

SOLID tidak dikirim oleh God Allmighty pada tablet, dan meskipun demikian, ada tingkat kebebasan untuk interpretasinya. Anda mungkin seidealistis yang Anda inginkan tentang hal itu, tetapi bantulah saya: jangan menyebarkan kepercayaan Anda ke komunitas. Setiap orang memiliki semua hak untuk menggunakan alat apa pun dengan cara apa pun yang dia inginkan dan melanggar semua aturan yang mungkin dia tahu (Anda tidak dapat menyalahkan pisau untuk pembunuhan). Apa yang mendefinisikan kualitas alat adalah keseimbangan antara permintaan untuk fitur tertentu dan penawaran untuk fitur tersebut. Dan cabang ini menunjukkan volume permintaan. Jika Anda tidak membutuhkan fitur ini - jangan gunakan. Saya bersedia. Dan banyak orang di sini juga membutuhkannya, dan _we_ memiliki kasus penggunaan praktis, sementara Anda mengatakan bahwa kita sebenarnya harus mengabaikannya. Ini hanya tentang apa yang lebih penting bagi pengelola - prinsip-prinsip suci (dengan cara apa pun mereka memahaminya), atau komunitas.

man, semua kasus penggunaan fitur proposal yang bagus, yang ini tidak

image

jadi saya hanya mengungkapkan rasa ingin tahu dan mengajukan pertanyaan, karena proposal tidak mengatakannya, mengapa Anda mungkin membutuhkannya

itu direbus menjadi khas: hanya karena, jangan berani

secara pribadi saya tidak peduli tentang solid atau oop, saya baru saja tumbuh terlalu lama, Anda membawanya dengan melemparkan argumen "antipattern uberclass" dan kemudian didukung ke "tingkat kebebasan untuk interpretasinya"

satu-satunya alasan praktis yang disebutkan dalam seluruh diskusi ini adalah ini: https://github.com/Microsoft/TypeScript/issues/14600#issuecomment -308362119

Kasus penggunaan yang sangat umum yang akan membantu adalah komponen React, yang merupakan kelas dengan properti statis seperti displayName, propTypes, dan defaultProps.

dan beberapa posting serupa https://github.com/Microsoft/TypeScript/issues/14600#issuecomment -345496014

tapi itu ditutupi oleh (new (...) => MyClass) & MyStaticInterface

itulah kasus penggunaan yang tepat dan alasan saya di sini. Apakah Anda melihat jumlah suara? Menurut Anda mengapa terserah Anda secara pribadi untuk memutuskan apa yang practical dan apa yang tidak? Practical adalah apa yang dapat dimasukkan ke practice , dan (pada saat penulisan) 83 orang akan menganggap fitur ini sangat praktis. Harap hormati orang lain dan baca utas lengkapnya sebelum Anda mulai menarik frasa keluar dari konteks dan menunjukkan berbagai kata kunci. Apa pun yang telah Anda atasi, itu jelas bukan ego Anda.

masuk akal bahwa hal-hal praktis adalah hal-hal yang memecahkan masalah, hal-hal non-praktis adalah hal-hal yang menggelitik rasa keindahan Anda, saya menghormati orang lain tetapi dengan semua rasa hormat itu, pertanyaan (kebanyakan retoris sekarang) masih ada: masalah apa yang ingin diajukan proposal ini memecahkan diberikan (new (...) => MyClass) & MyStaticInterface untuk https://github.com/Microsoft/TypeScript/issues/14600#issuecomment -308362119 dan https://github.com/Microsoft/TypeScript/issues/14600#issuecomment -289084844 hanya menyebabkan

tolong jangan di jawab

Untuk alasan yang sama saya terkadang menggunakan deklarasi type untuk mengurangi anotasi besar, saya pikir kata kunci seperti abstract static akan jauh lebih mudah dibaca daripada konstruksi yang relatif lebih sulit untuk dicerna seperti yang telah disebutkan sebagai yang sudah ada contoh.

Plus, kita masih belum membahas kelas abstrak?

Solusi untuk tidak menggunakan kelas abstrak bukanlah solusi menurut saya. Itu adalah solusi! Sebuah solusi sekitar apa?

Saya pikir permintaan fitur ini ada karena banyak orang, termasuk pemohon, telah menemukan bahwa fitur yang diharapkan seperti abstract static atau static di antarmuka tidak ada.

Berdasarkan solusi yang ditawarkan, apakah kata kunci static bahkan harus ada jika ada solusi untuk menghindari penggunaannya? Saya pikir itu akan sama konyolnya untuk disarankan.

Masalahnya di sini adalah bahwa static lebih masuk akal.
Berdasarkan minat yang dihasilkan, dapatkah kita melakukan diskusi yang tidak terlalu meremehkan?

Apakah ada pembaruan pada proposal ini? Adakah argumen yang layak dipertimbangkan yang menunjukkan mengapa kita tidak boleh memiliki static abstract dan sejenisnya?
Bisakah kami memiliki lebih banyak saran yang menunjukkan mengapa itu berguna?

Mungkin kita perlu menarik hal-hal dan meringkas apa yang telah dibahas dan sehingga kita dapat menemukan solusi.

Ada dua proposal yang saya pahami:

  1. antarmuka dan tipe dapat menentukan properti dan metode statis
interface ISerializable<T> { 
   static fromJson(json: string): T;
}
  1. kelas abstrak dapat mendefinisikan metode statis abstrak
abstract class MyClass<T> implements ISerializable<T> {
   abstract static fromJson(json: string): T;
}

class MyOtherClass extends MyClass<any> {
  static fromJson(json: string) {
  // unique implementation
  }
}

Adapun proposal satu, secara teknis ada solusi! Itu tidak bagus, tapi setidaknya itu sesuatu. Tapi itu ADALAH pekerjaan-sekitar.

Anda dapat membagi antarmuka Anda menjadi dua dan menulis ulang kelas Anda seperti

interface StaticInterface {
  new(...args) => MyClass;
  fromJson(json): MyClass;
}

interface InstanceInterface {
  toJson(): string;
}

const MyClass: StaticInterface = class implements InstanceInterface {
   ...
}

Menurut pendapat saya, ini banyak pekerjaan ekstra dan sedikit kurang mudah dibaca, dan memiliki kelemahan menulis ulang kelas Anda dengan cara yang lucu yang aneh dan menyimpang dari sintaks yang kami gunakan.

Tapi kemudian, bagaimana dengan proposal 2? Tidak ada yang bisa dilakukan tentang itu, bukan? Saya pikir itu layak ditangani juga!

Apa kegunaan praktis dari salah satu jenis ini—bagaimana salah satunya akan digunakan?

interface JsonSerializable {
    toJSON(): string;
    static fromJSON(serializedValue: string): JsonSerializable;
}

Sudah mungkin untuk mengatakan suatu nilai harus berupa objek seperti { fromJSON(serializedValue: string): JsonSerializable; } , jadi apakah ini hanya ingin untuk menegakkan suatu pola? Saya tidak melihat manfaatnya dari perspektif pengecekan tipe. Sebagai catatan tambahan: dalam hal ini akan memberlakukan pola yang sulit untuk dikerjakan—akan lebih baik untuk memindahkan proses serialisasi ke dalam kelas atau fungsi serializer yang terpisah karena berbagai alasan yang tidak akan saya bahas di sini.

Selain itu, mengapa hal seperti ini dilakukan?

class FirstChildClass extends AbstractParentClass {
    public static getSomeClassDependentValue(): string {
        return 'Some class-dependent value of class FirstChildClass';
    }
}

Bagaimana dengan menggunakan metode template atau pola strategi? Itu akan berhasil dan lebih fleksibel, bukan?

Saat ini, saya menentang fitur ini karena bagi saya tampaknya menambah kompleksitas yang tidak perlu untuk menggambarkan desain kelas yang sulit untuk dikerjakan. Mungkin ada beberapa manfaat yang saya lewatkan?

ada satu kasus penggunaan yang valid untuk metode Bereaksi statis, itu saja

@aleksey-bykov ah, oke. Untuk kasus tersebut, mungkin lebih baik jika menambahkan tipe pada properti constructor akan menyebabkan pengecekan tipe dalam skenario yang jarang terjadi. Sebagai contoh:

interface Component {
    constructor: ComponentConstructor;
}

interface ComponentConstructor {
    displayName?: string;
}

class MyComponent implements Component {
    static displayName = 5; // error
}

Itu tampaknya jauh lebih berharga bagi saya. Itu tidak menambah kerumitan tambahan pada bahasa dan hanya menambahkan lebih banyak pekerjaan untuk pemeriksa tipe saat memeriksa apakah kelas mengimplementasikan antarmuka dengan benar.


Omong-omong, saya berpikir kasus penggunaan yang valid adalah ketika bepergian dari sebuah instance ke konstruktor dan akhirnya ke metode atau properti statis dengan pemeriksaan tipe, tetapi itu sudah dimungkinkan dengan mengetikkan properti constructor pada sebuah tipe dan akan diselesaikan untuk instance kelas di #3841.

Apakah pola serialisasi/deserialisasi yang dijelaskan di atas tidak "valid"?

@dsherret tidak "melihat manfaat dari perspektif pengecekan tipe". Inti dari melakukan analisis statis di tempat pertama adalah untuk menangkap kesalahan sedini mungkin. Saya mengetik sesuatu sehingga jika tanda tangan panggilan perlu diubah, maka setiap orang yang memanggilnya -- atau, secara kritis, semua orang yang bertanggung jawab untuk menerapkan metode yang menggunakan tanda tangan tersebut -- memperbarui ke tanda tangan baru.

Misalkan saya memiliki perpustakaan yang menyediakan satu set kelas saudara dengan metode foo(x: number, y: boolean, z: string) statis, dan harapannya adalah bahwa pengguna akan menulis kelas pabrik yang mengambil beberapa kelas ini dan memanggil metode untuk membuat instance. (Mungkin deserialize atau clone atau unpack atau loadFromServer , tidak masalah.) Pengguna juga membuat subkelas dari induk yang sama (mungkin abstrak) kelas dari perpustakaan.

Sekarang, saya perlu mengubah kontrak itu sehingga parameter terakhir adalah objek opsi. Melewati nilai string untuk argumen ke-3 adalah kesalahan yang tidak dapat dipulihkan yang harus ditandai pada waktu kompilasi. Setiap kelas pabrik harus diperbarui untuk meneruskan objek saat memanggil foo , dan subkelas yang mengimplementasikan foo harus mengubah tanda tangan panggilannya.

Saya ingin menjamin bahwa pengguna yang memperbarui ke versi perpustakaan baru akan menangkap perubahan yang melanggar pada waktu kompilasi. Pustaka harus mengekspor salah satu solusi antarmuka statis di atas (seperti type MyClass = (new (data: number[]) => MyInterface) & MyStaticInterface; ) dan berharap konsumen menerapkannya di semua tempat yang tepat. Jika mereka lupa mendekorasi salah satu implementasinya atau jika mereka tidak menggunakan jenis library yang diekspor untuk mendeskripsikan tanda tangan panggilan foo di kelas pabrik mereka, kompiler tidak dapat memberi tahu apa pun yang berubah dan mereka mendapatkan kesalahan runtime . Bandingkan dengan implementasi metode abstract static yang masuk akal di kelas induk -- tidak diperlukan anotasi khusus, tidak ada beban pada kode yang dikonsumsi, itu hanya berfungsi di luar kotak.

@thw0rted tidakkah sebagian besar perpustakaan memerlukan semacam pendaftaran kelas-kelas ini dan pemeriksaan tipe dapat terjadi pada saat itu? Sebagai contoh:

// in the library code...
class SomeLibraryContext {
    register(classCtor: Function & { deserialize(serializedString: string): Component; }) {
        // etc...
    }
}

// then in the user's code
class MyComponent extends Comonent {
    static deserialize(serializedString: string) {
        return JSON.parse(serializedString) as Component;
    }
}

const context = new SomeLibraryContext();
// type checking occurs here today. This ensures `MyComponent` has a static deserialize method
context.register(MyComponent);

Atau apakah instance dari kelas-kelas ini digunakan dengan perpustakaan dan perpustakaan beralih dari instance ke konstruktor untuk mendapatkan metode statis? Jika demikian, dimungkinkan untuk mengubah desain perpustakaan agar tidak memerlukan metode statis.

Sebagai catatan tambahan, sebagai pelaksana antarmuka, saya akan sangat kesal jika saya dipaksa untuk menulis metode statis karena sangat sulit untuk menyuntikkan dependensi ke metode statis karena kurangnya konstruktor (tidak ada dependensi ctor yang dimungkinkan). Itu juga membuat sulit untuk menukar implementasi deserialization dengan sesuatu yang lain tergantung pada situasinya. Sebagai contoh, katakanlah saya melakukan deserializing dari sumber yang berbeda dengan mekanisme serialisasi yang berbeda ... sekarang saya memiliki metode deserialize statis yang dengan desain hanya dapat diimplementasikan satu cara untuk implementasi kelas. Untuk menyiasatinya, saya perlu memiliki properti atau metode statis global lain di kelas untuk memberi tahu metode deserialize statis apa yang harus digunakan. Saya lebih suka antarmuka Serializer terpisah yang memungkinkan untuk dengan mudah menukar apa yang harus digunakan dan tidak akan memasangkan (de)serialisasi ke kelas yang (de)serialisasi.

Saya mengerti maksud Anda -- untuk menggunakan pola pabrik, pada akhirnya Anda harus meneruskan kelas implementasi ke pabrik, dan pemeriksaan statis terjadi pada saat itu. Saya masih berpikir mungkin ada saat-saat ketika Anda ingin memberikan kelas yang sesuai dengan pabrik tetapi tidak benar-benar menggunakannya saat ini, dalam hal ini batasan abstract static akan menangkap masalah lebih awal dan membuat makna lebih jelas.

Saya punya contoh lain yang menurut saya tidak bertentangan dengan kekhawatiran "catatan sampingan" Anda. Saya memiliki sejumlah kelas saudara dalam proyek frontend, di mana saya memberi pengguna pilihan "penyedia" mana yang akan digunakan untuk fitur tertentu. Tentu saja, saya bisa membuat kamus di suatu tempat name => Provider dan menggunakan kunci untuk menentukan apa yang akan ditampilkan di daftar pilihan, tetapi cara saya menerapkannya sekarang adalah dengan membutuhkan bidang name statis pada setiap pelaksanaan Provider.

Pada titik tertentu, saya mengubahnya menjadi memerlukan shortName dan longName (untuk tampilan dalam konteks berbeda dengan jumlah ruang layar yang tersedia berbeda). Akan jauh lebih mudah untuk mengubah abstract static name: string; menjadi abstract static shortName: string; dll, daripada mengubah komponen daftar pilihan menjadi providerList: Type<Provider> & { shortName: string } & { longName: string } . Ini menyampaikan maksud, di tempat yang tepat (yaitu, di induk abstrak, bukan di komponen konsumsi), dan mudah dibaca serta mudah diubah. Saya pikir kita dapat mengatakan ada solusi, tetapi saya masih percaya itu secara objektif lebih buruk daripada perubahan yang diusulkan.

Saya baru-baru ini menemukan masalah ini, ketika saya perlu menggunakan metode statis dalam sebuah antarmuka. Saya minta maaf, jika ini sudah pernah dibahas sebelumnya, karena saya tidak punya waktu untuk membaca komentar sebanyak ini.

Masalah saya saat ini: Saya memiliki perpustakaan .js pribadi, yang ingin saya gunakan dalam proyek TypeScript saya. Jadi saya melanjutkan dan mulai menulis file .d.ts untuk perpustakaan itu, tetapi karena perpustakaan menggunakan metode statis, saya tidak bisa benar-benar menyelesaikannya. Apa pendekatan yang disarankan dalam kasus ini?

Terima kasih atas jawaban.

@greeny di sini adalah solusi yang berfungsi: https://github.com/Microsoft/TypeScript/issues/14600#issuecomment -437071092

khusus bagian ini:

type MyClass = (new (text: string) => MyInterface) & { myStaticMethod(): string; }

Kasus penggunaan lain: Kode yang dihasilkan / shim kelas parsial

  • Saya menggunakan perpustakaan @rsuter/nswag, yang menghasilkan spesifikasi angkuh.
  • Anda dapat menulis file 'ekstensi' yang digabungkan ke dalam file yang dihasilkan.
  • File ekstensi tidak pernah dijalankan, tetapi perlu dikompilasi dengan sendirinya!
  • Terkadang di dalam file itu saya perlu merujuk ke sesuatu yang belum ada (karena sudah dibuat)
  • Oleh karena itu saya ingin mendeklarasikan shim/antarmuka untuknya sebagai berikut:
  • Catatan: Anda dapat menentukan bahwa pernyataan import tertentu diabaikan dalam penggabungan akhir sehingga penyertaan 'shim' diabaikan dalam kode yang dihasilkan akhir.
interface SwaggerException
{
    static isSwaggerException(obj: any): obj is SwaggerException;
}

Tapi saya tidak bisa melakukan ini, dan harus benar-benar menulis kelas - yang baik-baik saja tetapi masih terasa salah. Saya hanya ingin - seperti yang dikatakan banyak orang lain - untuk mengatakan 'ini adalah kontraknya'

Hanya ingin menambahkan ini di sini karena saya tidak melihat penyebutan pembuatan kode lainnya - tetapi banyak komentar 'mengapa Anda membutuhkan ini' yang hanya menjengkelkan. Saya berharap persentase yang layak dari orang yang 'membutuhkan' fitur 'statis' ini di antarmuka melakukannya hanya agar mereka dapat merujuk ke item perpustakaan eksternal.

Saya juga sangat ingin melihat file d.ts yang lebih bersih - pasti ada beberapa yang tumpang tindih dengan fitur ini dan file-file itu. Sulit untuk memahami sesuatu seperti JQueryStatic karena sepertinya itu hanya peretasan. Juga kenyataannya adalah file d.ts sering kedaluwarsa dan tidak dipelihara dan Anda perlu mendeklarasikan shim sendiri.

(maaf untuk menyebutkan jQuery)

Untuk kasus serialisasi saya melakukan sesuatu seperti itu.

export abstract class World {

    protected constructor(json?: object) {
        if (json) {
            this.parseJson(json);
        }
    }

    /**
     * Apply data from a plain object to world. For example from a network request.
     * <strong i="6">@param</strong> json Parsed json object
     */
    abstract parseJson(json: object): void;

    /**
     * Parse instance to plane object. For example to send it through network.
     */
    abstract toJson(): object;
}

Tetapi akan lebih mudah untuk menggunakan sesuatu seperti itu:

export abstract class World {

    /**
     * Create a world from plain object. For example from a network request.
     * <strong i="10">@param</strong> json Parsed json object
     */
    abstract static fromJson(json: object): World;

    /**
     * Parse instance to plane object. For example to send it through network.
     */
    abstract toJson(): object;
}

Saya banyak membaca utas ini dan saya masih tidak mengerti mengapa baik dan benar untuk mengatakan tidak pada pola ini. Jika Anda berbagi pendapat itu, Anda juga mengatakan java dan bahasa populer lainnya salah. Atau aku salah?

Masalah ini terbuka dua tahun dan memiliki 79 komentar. Itu diberi label sebagai Awaiting More Feedback . Bisakah Anda mengatakan umpan balik apa lagi yang Anda butuhkan untuk mengambil keputusan?

benar-benar menjengkelkan bahwa saya tidak dapat menjelaskan metode statis baik di antarmuka maupun di kelas abstrak (hanya deklarasi). Sangat jelek untuk menulis solusi karena fitur ini belum diimplementasikan =(

Misalnya, next.js menggunakan fungsi getInitialProps statis untuk mendapatkan properti halaman sebelum membuat halaman. Jika terlempar, halaman tidak dibuat, melainkan halaman kesalahan.

https://github.com/zeit/next.js/blob/master/packages/next/README.md#fetching -data-and-component-lifecycle

Namun sayangnya, petunjuk yang mengimplementasikan metode ini dapat memberikan tanda tangan jenis apa pun, bahkan jika itu menyebabkan kesalahan saat dijalankan, karena tidak dapat diperiksa jenisnya.

Saya pikir masalah ini ada di sini untuk waktu yang lama karena JavaScript itu sendiri tidak bagus dalam hal statis

Warisan statis seharusnya tidak pernah ada. 🤔🤔.

Siapa yang ingin memanggil metode statis atau membaca bidang statis? 🤔🤔🤔.

  • kelas anak: Tidak boleh statis
  • kelas induk: gunakan argumen konstruktor
  • lainnya: gunakan antarmuka ISomeClassConstructor

Apakah ada kasus penggunaan lain?🤔🤔🤔🤔

Adakah pembaruan sama sekali tentang ini?

Jika ada bantuan, apa yang telah saya lakukan dalam kasus di mana saya perlu mengetik antarmuka statis untuk suatu kelas adalah menggunakan dekorator untuk menegakkan anggota statis di kelas

Dekorator didefinisikan sebagai:

export const statics = <T extends new (...args: Array<unknown>) => void>(): ((c: T) => void) => (_ctor: T): void => {};

Jika saya memiliki antarmuka anggota konstruktor statis yang didefinisikan sebagai

interface MyStaticType {
  new (urn: string): MyAbstractClass;
  isMember: boolean;
}

dan dipanggil pada kelas yang harus secara statis mendeklarasikan anggota di T sebagai:

@statics<MyStaticType>()
class MyClassWithStaticMembers extends MyAbstractClass {
  static isMember: boolean = true;
  // ...
}

Contoh yang paling sering adalah baik:

interface JsonSerializable {
    toJSON(): string;
    static fromJSON(serializedValue: string): JsonSerializable;
}

Tapi seperti yang dikatakan di #13462 di sini :

Antarmuka harus mendefinisikan fungsionalitas yang disediakan objek. Fungsionalitas ini harus dapat diganti dan dipertukarkan (itulah sebabnya metode antarmuka bersifat virtual). Statika adalah konsep paralel dengan perilaku dinamis/metode virtual.

Saya setuju pada poin bahwa antarmuka, dalam TypeScript, hanya menjelaskan contoh objek itu sendiri, dan bagaimana menggunakannya. Masalahnya adalah bahwa instance objek bukanlah definisi kelas , dan simbol static mungkin hanya ada pada definisi kelas .

Jadi saya dapat mengusulkan yang berikut, dengan segala kekurangannya:


Sebuah antarmuka bisa berupa menggambarkan suatu objek , atau kelas . Katakanlah antarmuka kelas dicatat dengan kata kunci class_interface .

class_interface ISerDes {
    serialize(): string;
    static deserialize(str: string): ISerDes
}

Kelas (dan antarmuka kelas) dapat menggunakan kata kunci statically implements untuk mendeklarasikan simbol statisnya menggunakan antarmuka objek ( antarmuka kelas tidak dapat diimplementasikan statically ).

Kelas (dan antarmuka kelas) masih akan menggunakan kata kunci implements dengan antarmuka objek atau antarmuka kelas .

Antarmuka kelas kemudian dapat memadukan antara antarmuka objek yang diimplementasikan secara statis dan antarmuka yang diimplementasikan instance. Dengan demikian, kita bisa mendapatkan yang berikut:

interface ISerializable{
    serialize(): string;
}
interface IDeserializable{
    deserialize(str: string): ISerializable
}

class_interface ISerDes implements ISerializable statically implements IDeserializable {}

Dengan cara ini, antarmuka dapat mempertahankan artinya, dan class_interface akan menjadi simbol abstraksi jenis baru yang didedikasikan untuk definisi kelas.

Satu kasus penggunaan kecil yang tidak kritis lagi:
Di Angular untuk kompilasi AOT, Anda tidak dapat memanggil fungsi di dekorator (seperti di dekorator modul @NgModule )
masalah sudut

Untuk modul service worker, Anda memerlukan hal seperti ini:
ServiceWorkerModule.register('ngsw-worker.js', {enabled: environment.production})
Lingkungan kami menggunakan subkelas, memperluas kelas abstrak dengan nilai default dan properti abstrak untuk diterapkan. Contoh implementasi serupa
Jadi AOT tidak berfungsi karena membangun kelas adalah fungsi, dan melempar kesalahan seperti: Function calls are not supported in decorators but ..

Untuk membuatnya berfungsi dan tetap mendukung pelengkapan otomatis/kompiler, dimungkinkan untuk mendefinisikan properti yang sama pada tingkat statis. Tetapi untuk properti 'untuk mengimplementasikan', kita memerlukan antarmuka dengan anggota statis atau anggota statis abstrak di kelas abstrak. Keduanya belum bisa.

dengan antarmuka dapat bekerja dengan cara ini:

// default.env.ts
interface ImplementThis {
  static propToImplement: boolean;
}

class DefaultEnv {
  public static production: boolean = false;
}

// my.env.ts
class Env extends DefaultEnv implements ImplementThis {
  public static propToImplement: true;
}

export const environment = Env;

dengan statika abstrak dapat bekerja dengan cara ini:

// default.env.ts
export abstract class AbstractDefaultEnv {
  public static production: boolean = false;
  public abstract static propToImplement: boolean;
}
// my.env.ts
class Env extends AbstractDefaultEnv {
  public static propToImplement: true;
}

export const environment = Env;

ada solusi , tetapi semuanya lemah :/

@DanielRosenwasser @RyanCavanaugh mohon maaf atas penyebutannya, tetapi tampaknya saran fitur ini—yang mendapat banyak dukungan dari komunitas, dan saya rasa akan cukup mudah diterapkan—telah terkubur jauh di dalam kategori Masalah. Apakah salah satu dari Anda memiliki komentar tentang fitur ini, dan apakah PR akan diterima?

Bukankah masalah ini merupakan duplikat dari #1263? 😛.

26398 (Ketik periksa anggota statis berdasarkan properti konstruktor tipe implementasi) sepertinya solusi yang lebih baik ... jika sesuatu seperti ini diimplementasikan maka saya harap itu yang itu. Itu tidak memerlukan sintaks/parsing tambahan dan hanya tipe pengecekan perubahan untuk satu skenario. Itu juga tidak menimbulkan banyak pertanyaan seperti ini.

Saya merasa seolah-olah metode statis dalam antarmuka tidak seintuitif memiliki metode abstrak statis pada kelas abstrak.

Saya pikir sedikit samar untuk menambahkan metode statis ke antarmuka karena antarmuka harus mendefinisikan objek, bukan kelas. Di sisi lain, kelas abstrak tentu saja harus diizinkan untuk memiliki metode abstrak statis, karena kelas abstrak digunakan untuk mendefinisikan subkelas. Sejauh implementasi ini berjalan, itu hanya perlu diperiksa tipenya ketika memperluas kelas abstrak (mis class extends MyAbstractClass ), bukan saat menggunakannya sebagai tipe (mis let myInstance: MyAbstractClass ).

Contoh:

abstract class MyAbstractClass {
  static abstract bar(): number;
}

class Foo extends MyAbstractClass {
  static bar() {
    return 42;
  }
}

sekarang karena kebutuhan saya menggunakan ini

abstract class MultiWalletInterface {

  static getInstance() {} // can't write a return type MultiWalletInterface

  static deleteInstance() {}

  /**
   * Returns new random  12 words mnemonic seed phrase
   */
  static generateMnemonic(): string {
    return generateMnemonic();
  }
}

ini tidak nyaman!

Saya datang dengan masalah di mana saya menambahkan properti ke "Objek", berikut adalah contoh kotak pasir

interface Object {
    getInstanceId: (object: any) => number;
}

Object.getInstanceId = () => 42;
const someObject = {};
Object.getInstanceId(someObject); // correct
someObject.getInstanceId({}); // should raise an error but does not

Setiap instance objek sekarang dianggap memiliki properti getInstanceId sementara hanya Object yang seharusnya. Dengan properti statis, masalahnya akan terpecahkan.

Anda ingin menambah ObjectConstructor, bukan Object. Anda mendeklarasikan metode instan, ketika Anda benar-benar ingin melampirkan metode ke konstruktor itu sendiri. Saya pikir ini sudah dimungkinkan melalui penggabungan deklarasi:

````ts
nyatakan global {
antarmuka ObjectConstructor {
halo(): string;
}
}

objek.halo();
````

@thw0rted Luar biasa! terima kasih saya tidak mengetahui ObjectConstructor

Poin yang lebih besar adalah Anda menambah tipe konstruktor daripada tipe instans. Saya baru saja mencari deklarasi Object di lib.es5.d.ts dan menemukan bahwa itu bertipe ObjectConstructor .

Sulit bagi saya untuk percaya bahwa masalah ini masih ada. Ini adalah fitur yang berguna secara sah, dengan beberapa kasus penggunaan yang sebenarnya.

Inti dari TypeScript adalah untuk dapat memastikan keamanan jenis dalam basis kode kami, jadi, mengapa fitur ini masih "menunggu umpan balik" setelah umpan balik selama dua tahun?

Saya mungkin jauh dalam hal ini, tetapi apakah sesuatu seperti metaclass Python dapat menyelesaikan masalah ini dengan cara asli yang disetujui (yaitu bukan peretasan atau solusi) tanpa melanggar paradigma TypeScript (yaitu menjaga jenis TypeScript terpisah untuk instance dan kelas)?

Sesuatu seperti ini:

interface DeserializingClass<T> {
    fromJson(serializedValue: string): T;
}

interface Serializable {
    toJson(): string;
}

class Foo implements Serializable metaclass DeserializingClass<Foo> {
    static fromJson(serializedValue: string): Foo {
        // ...
    }

    toJson(): string {
        // ...
    }
}

// And an example of how this might be used:
function saveObject(Serializable obj): void {
    const serialized: string = obj.toJson();
    writeToDatabase(serialized);
}

function retrieveObject<T metaclass DeserializingClass<T>>(): T {
    const serialized: string = getFromDatabase();
    return T.fromJson(serialized);
}

const foo: Foo = new Foo();
saveObject(foo);

const bar: Foo = retrieveObject<Foo>();

Sejujurnya bagian tersulit dari pendekatan ini sepertinya akan muncul dengan kata kunci TypeScript-y yang bermakna untuk metaclass ... staticimplements , classimplements , withstatic , implementsstatic ... tidak yakin.

Ini agak mirip dengan proposal @GerkinDev tetapi tanpa jenis antarmuka yang terpisah. Di sini, ada satu konsep antarmuka, dan mereka dapat digunakan untuk menggambarkan bentuk instance atau kelas. Kata kunci dalam definisi kelas implementasi akan memberi tahu kompiler sisi mana yang harus diperiksa dari setiap antarmuka.

Mari kita lanjutkan diskusi di #34516 dan #33892 tergantung pada fitur yang Anda pilih

Apakah halaman ini membantu?
0 / 5 - 0 peringkat