Typescript: Permintaan: Mutasi Dekorator Kelas

Dibuat pada 20 Sep 2015  ·  231Komentar  ·  Sumber: microsoft/TypeScript

Jika kami bisa mendapatkan ini untuk mengetik dengan benar, kami akan memiliki dukungan sempurna untuk mixin bebas boilerplate:

declare function Blah<T>(target: T): T & {foo: number}

<strong i="6">@Blah</strong>
class Foo {
    bar() {
        return this.foo; // Property 'foo' does not exist on type 'Foo'
    }
}

new Foo().foo; // Property 'foo' does not exist on type 'Foo'
Needs Proposal Suggestion

Komentar yang paling membantu

Hal yang sama akan berguna untuk metode:

class Foo {
  <strong i="6">@async</strong>
  bar(x: number) {
    return x || Promise.resolve(...);
  }
}

Dekorator async seharusnya mengubah tipe pengembalian menjadi Promise<any> .

Semua 231 komentar

Hal yang sama akan berguna untuk metode:

class Foo {
  <strong i="6">@async</strong>
  bar(x: number) {
    return x || Promise.resolve(...);
  }
}

Dekorator async seharusnya mengubah tipe pengembalian menjadi Promise<any> .

@Gaelan , inilah yang kami butuhkan di sini! Itu akan membuat mixin alami untuk digunakan.

class asPersistent {
  id: number;
  version: number;
  sync(): Promise<DriverResponse> { ... }
  ...
}

function PersistThrough<T>(driver: { new(): Driver }): (t: T) => T & asPersistent {
  return (target: T): T & asPersistent {
    Persistent.call(target.prototype, driver);
    return target;
  }
}

@PersistThrough(MyDBDriver)
Article extends TextNode {
  title: string;
}

var article = new Article();
article.title = 'blah';

article.sync() // Property 'sync' does not exist on type 'Article'

+1 untuk ini. Meskipun saya tahu ini sulit untuk diterapkan, dan mungkin lebih sulit untuk mencapai kesepakatan tentang semantik mutasi dekorator.

+1

Jika manfaat utama dari ini adalah memperkenalkan anggota tambahan ke tanda tangan jenis, Anda sudah dapat melakukannya dengan penggabungan antarmuka:

interface Foo { foo(): number }
class Foo {
    bar() {
        return this.foo();
    }
}

Foo.prototype.foo = function() { return 10; }

new Foo().foo();

Jika dekorator adalah fungsi aktual yang perlu dipanggil oleh kompiler untuk memutasikan kelas secara imperatif, ini sepertinya bukan hal idiomatis yang harus dilakukan dalam bahasa tipe yang aman, IMHO.

@masaeedu Apakah Anda tahu solusi apa pun untuk menambahkan anggota statis ke kelas yang didekorasi?

@davojan Tentu. Ini dia:

class A { }
namespace A {
    export let foo = function() { console.log("foo"); }
}
A.foo();

Ini juga akan berguna untuk dapat memperkenalkan _multiple_ properti ke kelas saat mendekorasi metode (misalnya, pembantu yang menghasilkan penyetel terkait untuk pengambil, atau sesuatu di sepanjang garis itu)

Pengetikan react-redux untuk connect mengambil komponen dan mengembalikan komponen yang dimodifikasi yang propsnya tidak menyertakan props terhubung yang diterima melalui redux, tetapi tampaknya TS tidak mengenali definisi connect mereka sebagai dekorator karena masalah ini. Apakah ada yang punya solusi?

Saya pikir definisi tipe ClassDecorator perlu diubah.

Saat ini declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void; . Mungkin itu bisa diubah menjadi

declare type MutatingClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
declare type WrappingClassDecorator = <TFunction extends Function, TDecoratorFunction extends Function>(target: TFunction) => TDecoratorFunction;
declare type ClassDecorator = MutatingClassDecorator | WrappingClassDecorator;

Jelas penamaannya menyebalkan dan saya tidak tahu apakah hal semacam ini akan berhasil (saya hanya mencoba mengonversi aplikasi Babel ke TypeScript dan sedang memukul ini).

@joyt Bisakah Anda memberikan rekonstruksi taman bermain dari masalah? Saya tidak menggunakan react-redux, tetapi seperti yang telah saya sebutkan sebelumnya, saya pikir ekstensi apa pun yang Anda inginkan untuk membentuk suatu tipe dapat dideklarasikan menggunakan penggabungan antarmuka.

@masaeedu di sini adalah rincian dasar dari bagian yang bergerak..

Pada dasarnya dekorator menyediakan banyak props ke komponen React, jadi tipe umum dekorator adalah subset dari komponen yang didekorasi, bukan superset.

Tidak yakin apakah ini membantu, tetapi mencoba mengumpulkan sampel yang tidak dapat dijalankan untuk menunjukkan kepada Anda jenis yang dimainkan.

// React types
class Component<TProps> {
    props: TProps
}
class ComponentClass<TProps> {
}
interface ComponentDecorator<TOriginalProps, TOwnProps> {
(component: ComponentClass<TOriginalProps>): ComponentClass<TOwnProps>;
}

// Redux types
interface MapStateToProps<TStateProps, TOwnProps> {
    (state: any, ownProps?: TOwnProps): TStateProps;
}

// Fake react create class
function createClass(component: any, props: any): any {
}

// Connect wraps the decorated component, providing a bunch of the properies
// So we want to return a ComponentDecorator which exposes LESS than
// the original component
function connect<TStateProps, TOwnProps>(
    mapStateToProps: MapStateToProps<TStateProps, TOwnProps>
): ComponentDecorator<TStateProps, TOwnProps> {
    return (ComponentClass) => {
        let mappedState = mapStateToProps({
            bar: 'bar value'
        })
        class Wrapped {
            render() {
                return createClass(ComponentClass, mappedState)
            }
        }

        return Wrapped
    }
}


// App Types
interface AllProps {
    foo: string
    bar: string
}

interface OwnProps {
    bar: string
}

// This does not work...
// @connect<AllProps, OwnProps>(state => state.foo)
// export default class MyComponent extends Component<AllProps> {
// }

// This does
class MyComponent extends Component<AllProps> {
}
export default connect<AllProps, OwnProps>(state => state.foo)(MyComponent)
//The type exported should be ComponentClass<OwnProps>,
// currently the decorator means we have to export ComponentClass<AllProps>

Jika Anda menginginkan contoh yang berfungsi penuh, saya sarankan menarik https://github.com/jaysoo/todomvc-redux-react-typescript atau contoh lain proyek react/redux/typescript.

Menurut https://github.com/wycats/javascript-decorators#class -declaration , pemahaman saya adalah bahwa declare type WrappingClassDecorator = <TFunction extends Function, TDecoratorFunction extends Function>(target: TFunction) => TDecoratorFunction; yang diusulkan tidak valid.

spek mengatakan:

@F("color")
<strong i="6">@G</strong>
class Foo {
}

diterjemahkan menjadi:

var Foo = (function () {
  class Foo {
  }

  Foo = F("color")(Foo = G(Foo) || Foo) || Foo;
  return Foo;
})();

Jadi jika saya memahaminya dengan benar, berikut ini seharusnya benar:

declare function F<T>(target: T): void;

<strong i="13">@F</strong>
class Foo {}

let a: Foo = new Foo(); // valid
class X {}
declare function F<T>(target: T): X;

<strong i="16">@F</strong>
class Foo {}

let a: X = new Foo(); // valid
let b: Foo = new Foo(); // INVALID
declare function F<T>(target: T): void;
declare function G<T>(target: T): void;

<strong i="19">@F</strong>
<strong i="20">@G</strong>
class Foo {}

let a: Foo = new Foo(); // valid
class X {}
declare function F<T>(target: T): void;
declare function G<T>(target: T): X;

<strong i="23">@F</strong>
<strong i="24">@G</strong>
class Foo {}

<strong i="25">@G</strong>
class Bar {}

<strong i="26">@F</strong>
class Baz {}

let a: Foo = new Foo(); // valid
let b: X = new Foo(); // INVALID
let c: X = new Bar(); // valid
let d: Bar = new Bar(); // INVALID
let e: Baz = new Baz(); // valid
class X {}
declare function F<T>(target: T): X;
declare function G<T>(target: T): void;

<strong i="29">@F</strong>
<strong i="30">@G</strong>
class Foo {}

<strong i="31">@G</strong>
class Bar {}

<strong i="32">@F</strong>
class Baz {}

let a: X = new Foo(); // valid
let b: Bar = new Bar(); // valid
let c: X = new Baz(); // valid
let d: Baz = new Baz(); // INVALID

@blai

Untuk contoh Anda:

class X {}
declare function F<T>(target: T): X;

<strong i="9">@F</strong>
class Foo {}

let a: X = new Foo(); // valid
let b: Foo = new Foo(); // INVALID

Saya berasumsi maksud Anda bahwa F mengembalikan kelas yang sesuai dengan X (dan bukan turunan dari X )? Misalnya:

declare function F<T>(target: T): typeof X;

Untuk kasus tersebut, pernyataan harus:

let a: X = new Foo(); // valid
let b: Foo = new Foo(); // valid

Foo yang ada dalam cakupan pernyataan let tersebut telah dimutasi oleh dekorator. Foo asli tidak lagi dapat dijangkau. Secara efektif setara dengan:

let Foo = F(class Foo {});

@nevir Ya, Anda benar. Terima kasih untuk klarifikasi.

Di samping catatan, sepertinya mematikan centang untuk membatalkan jenis kelas yang bermutasi relatif mudah:

diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index 06591a7..2320aff 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -11584,10 +11584,6 @@ namespace ts {
           */
         function getDiagnosticHeadMessageForDecoratorResolution(node: Decorator) {
             switch (node.parent.kind) {
-                case SyntaxKind.ClassDeclaration:
-                case SyntaxKind.ClassExpression:
-                    return Diagnostics.Unable_to_resolve_signature_of_class_decorator_when_called_as_an_expression;
-
                 case SyntaxKind.Parameter:
                     return Diagnostics.Unable_to_resolve_signature_of_parameter_decorator_when_called_as_an_expression;
         }

         /** Check a decorator */
        function checkDecorator(node: Decorator): void {
             const signature = getResolvedSignature(node);
             const returnType = getReturnTypeOfSignature(signature);
             if (returnType.flags & TypeFlags.Any) {
@@ -14295,9 +14291,7 @@ namespace ts {
             let errorInfo: DiagnosticMessageChain;
             switch (node.parent.kind) {
                 case SyntaxKind.ClassDeclaration:
-                    const classSymbol = getSymbolOfNode(node.parent);
-                    const classConstructorType = getTypeOfSymbol(classSymbol);
-                    expectedReturnType = getUnionType([classConstructorType, voidType]);
+                    expectedReturnType = returnType;
                     break;

                 case SyntaxKind.Parameter:
         }

Tetapi saya tidak cukup berpengetahuan untuk membuat kompiler mengeluarkan definisi tipe yang benar dari kelas yang bermutasi. Saya memiliki tes berikut:

tes/kasus/kesesuaian/dekorator/kelas/dekoratorOnClass10.ts

// <strong i="10">@target</strong>:es5
// <strong i="11">@experimentaldecorators</strong>: true
class X {}
class Y {}

declare function dec1<T>(target: T): T | typeof X;
declare function dec2<T>(target: T): typeof Y;

<strong i="12">@dec1</strong>
<strong i="13">@dec2</strong>
export default class C {
}

var c1: X | Y = new C();
var c2: X = new C();
var c3: Y = new C();

Ini menghasilkan tes/garis dasar/lokal/dekoratorOnClass10.types

=== tests/cases/conformance/decorators/class/decoratorOnClass10.ts ===
class X {}
>X : X

class Y {}
>Y : Y

declare function dec1<T>(target: T): T | typeof X;
>dec1 : <T>(target: T) => T | typeof X
>T : T
>target : T
>T : T
>T : T
>X : typeof X

declare function dec2<T>(target: T): typeof Y;
>dec2 : <T>(target: T) => typeof Y
>T : T
>target : T
>T : T
>Y : typeof Y

<strong i="17">@dec1</strong>
>dec1 : <T>(target: T) => T | typeof X

<strong i="18">@dec2</strong>
>dec2 : <T>(target: T) => typeof Y

export default class C {
>C : C
}

var c1: X | Y = new C();
>c1 : X | Y
>X : X
>Y : Y
>new C() : C
>C : typeof C

var c2: X = new C();
>c2 : X
>X : X
>new C() : C
>C : typeof C

var c3: Y = new C();
>c3 : Y
>Y : Y
>new C() : C
>C : typeof C

saya mengharapkan
>C: typeof C menjadi >C: typeof X | typeof Y

Bagi mereka yang tertarik dengan connect reaksi-redux sebagai studi kasus untuk fitur ini, saya telah mengajukan https://github.com/DefinitelyTyped/DefinitelyTyped/issues/9951 untuk melacak masalah di satu tempat.

Saya telah membaca semua komentar tentang masalah ini dan mendapat ide bahwa tanda tangan dekorator tidak benar-benar menunjukkan apa yang dapat dilakukannya dengan kelas yang dibungkus.

Pertimbangkan yang ini:

function decorator(target) {
    target.prototype.someNewMethod = function() { ... };
    return new Wrapper(target);
}

Itu harus diketik dengan cara itu:
declare function decorator<T>(target: T): Wrapper<T>;

Tetapi tanda tangan ini tidak memberi tahu kami bahwa dekorator telah menambahkan hal-hal baru ke prototipe target.

Di sisi lain, yang ini tidak memberi tahu kami bahwa dekorator sebenarnya telah mengembalikan pembungkus:
declare function decorator<T>(target: T): T & { someMethod: () => void };

Ada berita tentang ini? Ini akan sangat kuat untuk metaprogramming!

Bagaimana dengan pendekatan yang lebih sederhana untuk masalah ini? Untuk kelas yang didekorasi, kami mengikat nama kelas ke nilai kembalian dekorator, sebagai gula sintaksis.

declare function Blah<T>(target: T): T & {foo: number}

<strong i="6">@Blah</strong>
class Foo {
    bar() {
        return this.foo; // Property 'foo' does not exist on type 'Foo'
    }
}

// is desugared to
const Foo = Blah(class Foo {
  // this.foo is not available here
})

new Foo.foo // foo is available here.

Dari segi implementasi, ini akan memperkenalkan satu simbol sintetis untuk kelas yang didekorasi. Dan nama kelas asli hanya terikat pada ruang lingkup badan kelas.

@HerringtonDarkholme Saya pikir itu akan menjadi pendekatan pragmatis yang baik yang akan memberikan sebagian besar ekspresi yang diinginkan. Ide yang hebat!

Saya pasti ingin melihat ini suatu hari nanti

Saya sering menulis kelas untuk Angular 2 atau untuk Aurelia, yang terlihat seperti ini:

import {Http} from 'aurelia-fetch-client';
import {User} from 'models';

// accesses backend routes for 'api/user'
<strong i="9">@autoinject</strong> export default class UserService {
  constructor(readonly http : Http) { }

  readonly resourceUrl = 'api/users';

  async get(id: number) {
    const response = await this.http.fetch(this.resourceUrl);
    const user = await response.json() as User;
    return user;
  }

  async post(id: number, model: { [K in keyof User]?: User[K] }) {
    const response = await this.http.post(`${this.resourceUrl}/`${id}`, model);
    return await response.json();
  }
}

Apa yang ingin saya tulis adalah sesuatu seperti
dekorator/api-client.ts

import {Http} from 'aurelia-fetch-client';

export type Target = { name; new (...args): { http: Http }};

export default function apiClient<T extends { id: string }>(resourceUrl: string) {
  return (target: Target)  => {
    type AugmentedTarget = Target & { get(id: number): Promise<T>, post(id, model: Partial<T>) };
    const t = target as AugmentedTarget;
    t.prototype.get = async function (id: number) {
      const response = await this.http.fetch(resourceUrl);
      return await response.json() as T;
    }
  }
}

dan kemudian saya bisa menerapkannya secara umum seperti

import {Http} from 'aurelia-fetch-client';
import apiClient from ./decorators/api-client
import {User} from 'models';

@apiClient<User>('api/users') export default class UserService {
  constructor(readonly http : Http) { }
}

tanpa kehilangan typesafety. Ini akan menjadi keuntungan untuk menulis kode yang bersih dan ekspresif.

Menghidupkan kembali masalah ini.

Sekarang #13743 keluar dan dukungan mixin dalam bahasa ini adalah fitur yang sangat berguna.

@HerringtonDarkholme kurang cocok untuk kasus ini, harus mendeklarasikan tipe pengembalian dekorator kehilangan beberapa fitur dinamis ...

@ahejlsberg , @mhegazy Apakah menurut Anda ini bisa dilakukan?

Saya memiliki skenario penggunaan lain yang saya tidak yakin belum tercakup oleh percakapan ini tetapi mungkin berada di bawah payung yang sama.

Saya ingin menerapkan dekorator metode yang mengubah tipe metode sepenuhnya (bukan tipe pengembalian atau parameter tetapi seluruh fungsi). misalnya

type AsyncTask<Method extends Function> = {
    isRunning(): boolean;
} & Method;

// Decorator definition...
function asyncTask(target, methodName, descriptor) {
    ...
}

class Order {
    <strong i="7">@asyncTask</strong>
    async save(): Promise<void> {
        // Performs an async task and returns a promise
        ...
    }
}

const order = new Order();

order.save();
order.save.isRunning(); // Returns true

Sangat mungkin dalam JavaScript, itu jelas bukan masalahnya, tetapi dalam TypeScript saya memerlukan dekorator asyncTask untuk mengubah jenis metode yang didekorasi dari () => Promise<void> menjadi AsyncTask<() => Promise<void>> .

Cukup yakin ini tidak mungkin sekarang dan mungkin berada di bawah payung masalah ini?

@codeandcats contoh Anda adalah kasus penggunaan yang sama persis dengan saya di sini!

Hai @ohjames , maafkan saya, saya mengalami masalah dengan contoh Anda, ada kemungkinan Anda bisa menulis ulang menjadi sesuatu yang berfungsi seperti di taman bermain?

Ada kemajuan dalam hal ini? Saya memiliki ini di kepala saya sepanjang hari, tidak menyadari masalah ini, pergi untuk mengimplementasikannya hanya untuk mengetahui bahwa kompiler tidak memahaminya. Saya memiliki proyek yang dapat menggunakan solusi logging yang lebih baik, jadi saya menulis singleton cepat untuk kemudian berkembang menjadi logger penuh yang akan saya lampirkan ke kelas melalui dekorator seperti

<strong i="6">@loggable</strong>
class Foo { }

dan saya menulis kode yang diperlukan untuk itu

type Loggable<T> = T & { logger: Logger };

function loggable<T extends Function>(target: T): Loggable<T>
{
    Object.defineProperty(target.prototype, 'logger',
        { value: Logger.instance() });
    return <Loggable<T>> target;
}

dan properti logger pasti ada saat runtime tetapi sayangnya tidak diambil oleh kompiler.

Saya ingin melihat beberapa resolusi untuk masalah ini, terutama karena konstruk runtime seperti ini harus benar-benar dapat direpresentasikan dengan benar pada waktu kompilasi.

Saya akhirnya memilih dekorator properti hanya untuk membantu saya saat ini:

function logger<T>(target: T, key: string): void
{
    Object.defineProperty(target, 'logger',
        { value: Logger.instance() });
}

dan melampirkannya ke kelas seperti

class Foo {
    <strong i="19">@logger</strong> private logger: Logger;
    ...

tapi ini jauh lebih boilerplate per kelas yang menggunakan logger daripada dekorator kelas @loggable sederhana. Saya kira saya dapat melakukan typecast seperti (this as Loggable<this>).logger tetapi ini juga cukup jauh dari ideal, terutama setelah melakukannya beberapa kali. Itu akan sangat cepat melelahkan.

Saya harus dapat TS untuk seluruh aplikasi terutama karena saya tidak dapat membuat https://github.com/jeffijoe/mobx-task bekerja dengan dekorator. Saya berharap ini akan segera ditangani. 😄.

Ini sangat menjengkelkan di ekosistem Angular 2 di mana dekorator dan TypeScript diperlakukan sebagai warga negara kelas satu. Namun begitu Anda mencoba menambahkan properti dengan dekorator, kompiler TypeScript mengatakan tidak. Saya akan berpikir tim Angular 2 akan menunjukkan minat pada masalah ini.

@zajrik Anda dapat mencapai apa yang Anda inginkan dengan mixin kelas yang telah didukung dengan pengetikan yang tepat sejak TS 2.2:

Tentukan mixin Loggable Anda seperti:

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

interface Logger {}

// You don't strictly need this interface, type inference will determine the shape of Loggable,
// you only need it if you want to refer to Loggable in a type position.
interface Loggable {
  logger: Logger;
}

function Loggable<T extends Constructor<object>>(superclass: T) {
  return class extends superclass {
    logger: Logger;
  };
}

dan kemudian Anda dapat menggunakannya dalam beberapa cara. Baik dalam klausa extends dari deklarasi kelas:

class Foo {
  superProperty: string;
}

class LoggableFoo extends Loggable(Foo) {
  subProperty: number;
}

TS tahu bahwa instance LoggableFoo memiliki superProperty , logger , dan subProperty :

const o = new LoggableFoo();
o.superProperty; // string
o.logger; // Logger
o.subProperty; // number

Anda juga dapat menggunakan mixin sebagai ekspresi yang mengembalikan kelas beton yang ingin Anda gunakan:

const LoggableFoo = Loggable(Foo);

Anda _can_ juga menggunakan mixin kelas sebagai dekorator, tetapi memiliki beberapa semantik yang sedikit berbeda, terutama yang mensubklasifikasikan kelas Anda, daripada membiarkan kelas Anda mensubklasifikasikannya.

Mixin kelas memiliki beberapa keunggulan dibandingkan dekorator, IMO:

  1. Mereka membuat superclass baru, sehingga kelas tempat Anda menerapkannya memiliki perubahan untuk menimpanya
  2. Mereka mengetik cek sekarang, tanpa fitur tambahan dari TypeScript
  3. Mereka bekerja dengan baik dengan inferensi tipe - Anda tidak perlu mengetikkan nilai balik dari fungsi mixin
  4. Mereka bekerja dengan baik dengan analisis statis, terutama lompat ke definisi - Melompat ke implementasi logger membawa Anda ke mixin _implementation_, bukan antarmuka.

@justinfagnani Saya bahkan belum mempertimbangkan mixin untuk ini, jadi terima kasih. Saya akan melanjutkan dan menulis mixin Loggable malam ini untuk membuat sintaks lampiran Logger saya sedikit lebih bagus. Rute extends Mixin(SuperClass) lebih saya sukai karena sejauh ini saya menggunakan mixin sejak rilis TS 2.2.

Namun, saya lebih suka ide sintaks dekorator daripada mixin, jadi saya masih berharap beberapa resolusi dapat diperoleh untuk masalah khusus ini. Mampu membuat mixin bebas boilerplate menggunakan dekorator akan menjadi keuntungan besar untuk kode yang lebih bersih, menurut saya.

@zajrik senang sarannya membantu, semoga

Saya masih tidak begitu mengerti bagaimana mixin memiliki lebih banyak boilerplate daripada dekorator. Mereka hampir identik dalam bobot sintaksis:

Campuran Kelas:

class LoggableFoo extends Loggable(Foo) {}

vs Dekorator:

<strong i="12">@Loggable</strong>
class LoggableFoo extends Foo {}

Menurut pendapat saya, mixin jauh lebih jelas tentang tujuannya: menghasilkan superclass, dan superclass mendefinisikan anggota kelas, jadi mixin mungkin juga mendefinisikan anggota.

Dekorator akan digunakan untuk banyak hal sehingga Anda tidak dapat berasumsi bahwa itu adalah atau tidak mendefinisikan anggota. Itu bisa saja mendaftarkan kelas untuk sesuatu, atau mengaitkan beberapa metadata dengannya.

Agar adil saya pikir apa yang diinginkan @zajrik adalah:

<strong i="7">@loggable</strong>
class Foo { }

Yang tidak dapat disangkal, jika sedikit, lebih sedikit boilerplate.

Yang mengatakan, saya suka solusi mixin. Saya terus lupa bahwa mixin adalah suatu hal.

Jika semua yang Anda pedulikan adalah menambahkan properti ke kelas saat ini, maka mixin pada dasarnya setara dengan dekorator dengan satu gangguan signifikan... jika kelas Anda belum memiliki superkelas, Anda perlu membuat superkelas kosong untuk menggunakannya. Juga sintaks tampaknya lebih berat secara umum. Juga tidak jelas apakah mixin parametrik didukung (apakah extends Mixin(Class, { ... }) diperbolehkan).

@justinfagnani dalam daftar alasan Anda, poin 2-4 sebenarnya adalah kekurangan dalam TypeScript bukan keuntungan dari mixin. Mereka tidak berlaku di dunia JS.

Saya pikir kita semua harus jelas bahwa solusi berbasis mixin untuk masalah OP akan melibatkan penambahan dua kelas ke rantai prototipe, salah satunya tidak berguna. Ini mencerminkan perbedaan semantik dari mixin Vs dekorator, mixin memberi Anda kesempatan untuk mencegat rantai kelas induk. Namun 95% dari waktu ini bukan yang ingin dilakukan orang, mereka ingin mendekorasi kelas ini . Sementara mixin memiliki kegunaan terbatas, saya pikir mempromosikannya sebagai alternatif untuk dekorator dan kelas tingkat tinggi secara semantik tidak pantas.

Mixin pada dasarnya setara dengan dekorator dengan satu gangguan signifikan... jika kelas Anda belum memiliki superclass, Anda perlu membuat superclass kosong untuk menggunakannya

Ini belum tentu benar:

function Mixin(superclass = Object) { ... }

class Foo extends Mixin() {}

Juga sintaks tampaknya lebih berat secara umum.

Saya hanya tidak mengerti bagaimana ini terjadi, jadi kita harus tidak setuju.

Juga tidak jelas apakah mixin parametrik didukung (diperpanjang Mixin(Class, { ... }) diperbolehkan).

Mereka sangat banyak. Mixin hanyalah fungsi.

dalam daftar alasan Anda, poin 2-4 sebenarnya adalah kekurangan dalam TypeScript, bukan keuntungan dari mixin. Mereka tidak berlaku di dunia JS.

Ini adalah masalah TypeScript, jadi mereka berlaku di sini. Di dunia JS, dekorator sebenarnya belum ada.

Saya pikir kita semua harus jelas bahwa solusi berbasis mixin untuk masalah OP akan melibatkan penambahan dua kelas ke rantai prototipe, salah satunya tidak berguna.

Saya tidak jelas dari mana Anda mendapatkan dua. Itu satu, seperti yang mungkin dilakukan dekorator, kecuali jika itu ditambal. Dan prototipe mana yang tidak berguna? Aplikasi mixin mungkin menambahkan properti/metode, itu tidak berguna.

Ini mencerminkan perbedaan semantik dari mixin Vs dekorator, mixin memberi Anda kesempatan untuk mencegat rantai kelas induk. Namun 95% dari waktu ini bukan yang ingin dilakukan orang, mereka ingin mendekorasi kelas ini.

Saya tidak begitu yakin ini benar. Biasanya saat mendefinisikan kelas, Anda mengharapkannya berada di bagian bawah hierarki pewarisan, dengan kemampuan untuk mengganti metode superkelas. Dekorator harus menambal kelas, yang memiliki banyak masalah, termasuk tidak bekerja dengan super() , atau memperluasnya, dalam hal ini kelas yang didekorasi tidak memiliki kemampuan untuk menimpa ekstensi. Ini dapat berguna dalam beberapa kasus, seperti dekorator yang menimpa setiap metode kelas yang ditentukan untuk penelusuran kinerja/debugging, tetapi ini jauh dari model pewarisan biasa.

Sementara mixin memiliki kegunaan terbatas, saya pikir mempromosikannya sebagai alternatif untuk dekorator dan kelas tingkat tinggi secara semantik tidak pantas.

Ketika seorang pengembang ingin menambahkan anggota ke rantai prototipe, mixin benar-benar sesuai secara semantik. Dalam setiap kasus yang saya lihat seseorang ingin menggunakan dekorator untuk mixin, menggunakan class mixin akan menyelesaikan tugas yang sama, dengan semantik yang sebenarnya mereka harapkan dari dekorator, lebih banyak fleksibilitas karena properti yang berfungsi dengan panggilan super, dan Tentu saja mereka bekerja sekarang.

Mixin hampir tidak pantas ketika mereka secara langsung menangani kasus penggunaan.

Ketika seorang pengembang ingin menambahkan anggota ke rantai prototipe

Itulah poin saya, OP tidak ingin menambahkan apa pun ke rantai prototipe. Dia hanya ingin mengubah satu kelas, dan kebanyakan ketika orang menggunakan dekorator, mereka bahkan tidak memiliki kelas induk selain Object. Dan untuk beberapa alasan Mixin(Object) tidak berfungsi di TypeScript sehingga Anda harus menambahkan kelas kosong dummy. Jadi sekarang Anda memiliki rantai prototipe 2 (tidak termasuk Objek) saat Anda tidak membutuhkannya. Plus ada biaya non-sepele untuk menambahkan kelas baru ke rantai prototipe.

Adapun sintaks membandingkan Mixin1(Mixin2(Mixin3(Object, { ... }), {... }), {...}) . Parameter untuk setiap mixin sejauh mungkin dari kelas mixin. Sintaks dekorator jelas lebih mudah dibaca.

Meskipun sintaks dekorator per-se tidak mengetik centang, Anda bisa menggunakan pemanggilan fungsi biasa untuk mendapatkan apa yang Anda inginkan:

class Logger { static instance() { return new Logger(); } }
type Loggable<T> = T & { logger: Logger };
function loggable<T, U>(target: { new (): T } & U): { new (): Loggable<T> } & U
{
    // ...
}

const Foo = loggable(class {
    x: string
});

let foo = new Foo();
foo.logger; // Logger
foo.x; // string

Hanya sedikit menjengkelkan bahwa Anda harus mendeklarasikan kelas Anda sebagai const Foo = loggable(class { , tetapi selain itu semuanya berfungsi.

@ohjames (cc @justinfagnani) Anda harus berhati-hati saat memperluas bawaan seperti Object (karena mereka sering mem-bash prototipe subkelas Anda): https://github.com/Microsoft/TypeScript/wiki/FAQ #mengapa -tidak-memperpanjang-bawaan-seperti-kesalahan-array-dan-peta-kerja

@nevir ya, saya sudah mencoba saran @justinfagnani menggunakan mixin dengan default Object parameter di masa lalu dengan TypeScript 2.2 dan tsc menolak kode.

@ohjames masih berfungsi, Anda hanya perlu berhati-hati dengan prototipe dalam kasus default (lihat entri FAQ itu).

Padahal, umumnya lebih mudah untuk mengandalkan perilaku tslib.__extend ketika melewati null

Adakah rencana untuk memfokuskan masalah ini pada langkah iterasi berikutnya? Manfaat fitur ini sangat tinggi di banyak perpustakaan.

Saya baru saja mengalami masalah ini - ini memaksa saya untuk menulis banyak kode yang tidak dibutuhkan. Menyelesaikan masalah ini akan sangat membantu kerangka/perpustakaan berbasis dekorator.

@TomMarius Seperti yang telah saya sebutkan sebelumnya, kelas yang dibungkus dengan fungsi dekorator sudah mengetik centang dengan benar, Anda tidak dapat menggunakan gula sintaks @ . Alih-alih melakukan:

<strong i="8">@loggable</strong>
class Foo { }

Anda hanya perlu melakukan:

const Foo = loggable(class { });

Anda bahkan dapat menyusun banyak fungsi dekorator bersama-sama sebelum membungkus kelas di dalamnya. Meskipun membuat sintaks gula berfungsi dengan baik itu berharga, sepertinya ini tidak akan menjadi titik sakit yang sangat besar.

@masaeedu Sungguh masalahnya bukan eksternal tetapi dukungan tipe internal. Mampu menggunakan properti yang ditambahkan dekorator di dalam kelas itu sendiri tanpa kesalahan kompilasi adalah hasil yang diinginkan, setidaknya untuk saya. Contoh yang Anda berikan hanya akan memberikan Foo tipe yang dapat dicatat tetapi tidak akan memberikan tipe tersebut ke definisi kelas itu sendiri.

@zajrik Seorang dekorator mengembalikan kelas baru dari kelas asli, bahkan ketika Anda menggunakan sintaks @ bawaan. Jelas JS tidak menegakkan kemurnian, jadi Anda bebas untuk mengubah kelas asli yang Anda lewati, tetapi ini tidak sesuai dengan penggunaan konsep dekorator secara idiomatis. Jika Anda menggabungkan erat fungsionalitas yang Anda tambahkan melalui dekorator ke internal kelas, mereka mungkin juga merupakan properti internal.

Bisakah Anda memberi saya contoh kasus penggunaan untuk kelas yang menggunakan API secara internal yang ditambahkan di beberapa titik nanti melalui dekorator?

Contoh Logger di atas adalah contoh yang baik dari _want_ umum untuk dapat memanipulasi internal kelas yang didekorasi. (Dan akrab bagi orang-orang yang datang dari bahasa lain dengan dekorasi kelas; seperti Python )

Yang mengatakan, saran mixin kelas @justinfagnani sepertinya merupakan alternatif yang baik untuk kasus itu

Jika Anda ingin dapat mendefinisikan internal kelas, cara terstruktur untuk melakukannya bukanlah dengan menambal kelas, atau mendefinisikan subkelas baru, yang keduanya akan sulit dipikirkan oleh TypeScript dalam konteks kelas. itu sendiri, tetapi untuk hanya mendefinisikan hal-hal di kelas itu sendiri, atau membuat superclass baru yang memiliki properti yang diperlukan, yang dapat dipikirkan oleh TypeScript.

Dekorator benar-benar tidak boleh mengubah bentuk kelas dengan cara yang terlihat oleh kelas atau sebagian besar konsumen. @masaeedu ada di sini.

Meskipun apa yang Anda katakan itu benar, TypeScript tidak ada di sana untuk menerapkan praktik pengkodean yang bersih, tetapi untuk mengetik kode JavaScript dengan benar, dan dalam kasus ini gagal.

@masaeedu Apa yang dikatakan @zajrik . Saya memiliki dekorator yang mendeklarasikan "layanan online" yang menambahkan banyak properti ke kelas yang kemudian digunakan di kelas. Mensubklasifikasikan atau mengimplementasikan antarmuka bukanlah pilihan karena metadata yang hilang dan penegakan batasan (jika Anda berusaha untuk tidak ada duplikasi kode).

@TomMarius Maksud saya adalah mengetik dengan benar. Saat Anda menerapkan fungsi dekorator ke kelas, kelas tidak diubah dengan cara apa pun. Kelas baru diproduksi melalui beberapa transformasi kelas asli, dan hanya kelas baru ini yang dijamin mendukung API yang diperkenalkan oleh fungsi dekorator.

Saya tidak tahu apa artinya "metadata yang hilang dan penegakan batasan" (mungkin contoh nyata akan membantu), tetapi jika kelas Anda secara eksplisit bergantung pada API yang diperkenalkan oleh dekorator, itu hanya harus mensubklasifikasikannya secara langsung melalui pola mixin yang ditunjukkan @justinfagnani , atau menyuntikkannya melalui konstruktor atau sesuatu. Kegunaan dekorator adalah bahwa mereka mengizinkan kelas-kelas yang tertutup untuk modifikasi diperluas untuk kepentingan kode yang menggunakan kelas-kelas tersebut . Jika Anda bebas mendefinisikan kelas sendiri, gunakan saja extends .

@masaeedu Jika Anda sedang mengembangkan semacam, katakanlah, perpustakaan RPC, dan Anda ingin memaksa pengguna untuk menulis metode asinkron saja, pendekatan berbasis warisan memaksa Anda untuk menggandakan kode (atau saya belum menemukan cara yang benar , mungkin - Saya akan senang jika Anda memberi tahu saya jika Anda tahu caranya).

Pendekatan berbasis warisan
Definisi: export abstract class Service<T extends { [P in keyof T]: () => Promise<IResult>}> { protected someMethod(): Promise<void> { return Promise.reject(""); } }
Penggunaan: export default class MyService extends Service<MyService> { async foo() { return this.someMethod(); } }

Pendekatan berbasis dekorator
Definisi: export function service<T>(target: { new (): T & { [P in keyof T]: () => Promise<IResult> } }) { target.someMethod = function () { return Promise.reject(""); }; return target; }
Penggunaan: <strong i="17">@service</strong> export default class { async foo() { return this.someMethod(); } }

Anda dapat dengan jelas melihat duplikasi kode dalam contoh pendekatan berbasis pewarisan. Telah terjadi berkali-kali pada saya dan pengguna saya bahwa mereka lupa mengubah parameter type ketika mereka menyalin-menempelkan kelas atau mulai menggunakan "any" sebagai parameter type dan perpustakaan berhenti bekerja untuk mereka; pendekatan berbasis dekorator jauh lebih ramah pengembang.

Setelah itu ada masalah lain dengan pendekatan berbasis pewarisan: metadata refleksi hilang sekarang, jadi Anda harus lebih banyak menduplikasi kode karena Anda harus memperkenalkan dekorator service . Penggunaannya sekarang: <strong i="22">@service</strong> export default class MyService extends Service<MyService> { async foo() { return this.someMethod(); } } , dan itu jelas tidak ramah, bukan hanya sedikit ketidaknyamanan.

Anda benar bahwa secara semantik modifikasi dilakukan setelah kelas didefinisikan, namun, tidak ada cara untuk membuat instance kelas yang tidak didekorasi, jadi tidak ada alasan untuk tidak mendukung mutasi kelas dengan benar selain itu terkadang memungkinkan kode yang tidak bersih (tetapi terkadang untuk kebaikan yang lebih baik). Ingat bahwa JavaScript masih didasarkan pada prototipe, sintaks kelas hanyalah gula untuk menutupinya. Prototipe bisa berubah dan mungkin dimatikan oleh dekorator, dan harus diketik dengan benar.

Saat Anda menerapkan fungsi dekorator ke kelas, kelas tidak diubah dengan cara apa pun.

Tidak benar, ketika Anda menerapkan fungsi dekorator ke kelas, kelas dapat diubah, dengan cara apa pun. Apakah Anda suka atau tidak.

@TomMarius Anda mencoba mengeksploitasi inferensi untuk menegakkan beberapa kontrak, yang sama sekali tidak relevan dengan argumen yang ada di sini. Anda hanya harus melakukan:

function service<T>(target: { new (): T & {[P in keyof T]: () => Promise<any> } }) { return target };

// Does not type check
const MyWrongService = service(class {
    foo() { return ""; }
})

// Type checks
const MyRightService = service(class {
    async foo() { return ""; }
})

Sama sekali tidak ada persyaratan bagi internal kelas untuk mengetahui fungsi dekorasi.

@masaeedu Bukan itu maksud saya. Dekorator layanan/kelas Layanan memperkenalkan beberapa properti baru, dan ini selalu tersedia untuk kelas yang akan digunakan, tetapi sistem tipe tidak mencerminkan hal itu dengan benar. Anda mengatakan saya harus menggunakan warisan untuk itu, jadi saya telah menunjukkan kepada Anda mengapa saya tidak bisa/tidak mau.

Saya telah mengedit contoh agar lebih jelas.

@masaeedu Omong-omong, pernyataan "Seorang dekorator mengembalikan kelas baru dari kelas asli" salah - setiap dekorator yang kami berdua tunjukkan di sini mengembalikan kelas asli yang bermutasi atau langsung, tidak pernah yang baru.

@TomMarius Komentar Anda menyebutkan masalah dengan "memaksa pengguna untuk menulis metode asinkron saja", yang merupakan masalah yang saya coba atasi dalam komentar saya. Menegakkan bahwa pengguna telah mengikuti kontrak yang Anda harapkan harus dilakukan di mana pun kode pengguna diteruskan kembali ke perpustakaan, dan tidak ada hubungannya dengan diskusi tentang apakah dekorator harus mengubah tipe-bentuk yang disajikan ke internal kelas. Masalah ortogonal dalam menyediakan API ke kode pengguna dapat diselesaikan dengan pendekatan pewarisan atau komposisi standar.

@ohjames Kelas tidak diubah hanya dengan menerapkan dekorator. JS tidak memaksakan kemurnian, jadi jelas pernyataan apa pun di mana pun dalam kode Anda dapat mengubah hal lain, tetapi ini tidak relevan dengan diskusi fitur ini. Bahkan setelah fitur diimplementasikan, TypeScript tidak akan membantu Anda melacak perubahan struktural yang berubah-ubah di dalam badan fungsi.

@masaeedu Anda memilih sedikit, tapi saya sedang berbicara tentang gambaran besarnya. Harap tinjau semua komentar saya di utas ini - intinya bukan pada masalah individu tetapi pada setiap masalah yang terjadi pada saat yang bersamaan. Saya pikir saya menjelaskan masalah dengan pendekatan berbasis warisan dengan baik - banyak dan banyak duplikasi kode.

Untuk kejelasan, ini bukan tentang "kode bersih" bagi saya. Masalahnya adalah salah satu kepraktisan; Anda tidak perlu perubahan besar-besaran pada sistem tipe jika Anda memperlakukan @foo sama dengan aplikasi fungsi. Jika Anda membuka kaleng worm untuk mencoba memperkenalkan informasi tipe ke argumen fungsi dari tipe pengembaliannya , sementara pada saat yang sama berinteraksi dengan inferensi tipe dan semua binatang ajaib lainnya yang ditemukan di berbagai sudut sistem tipe TypeScript, saya merasa ini akan menjadi hambatan besar bagi fitur-fitur baru seperti halnya overloading sekarang.

@TomMarius Komentar pertama Anda di utas ini adalah tentang kode bersih, yang tidak relevan. Komentar selanjutnya adalah tentang konsep layanan online yang Anda berikan contoh kodenya. Keluhan utama, mulai dari paragraf pertama hingga keempat, adalah tentang bagaimana rawan kesalahan untuk menggunakan MyService extends Service<MyService> . Saya mencoba menunjukkan contoh bagaimana Anda bisa menangani ini.

Saya telah melihatnya lagi, dan saya benar-benar tidak dapat melihat apa pun dalam contoh itu yang menggambarkan mengapa anggota kelas yang didekorasi perlu mengetahui dekorator. Ada apa dengan properti baru yang Anda berikan kepada pengguna yang tidak dapat dicapai dengan pewarisan standar? Saya belum pernah bekerja dengan refleksi, jadi saya membaca sekilas tentang itu, maaf.

@masaeedu Saya dapat mencapainya dengan warisan, tetapi warisan memaksa saya/pengguna saya untuk menduplikasi kode secara besar-besaran, jadi saya ingin memiliki cara lain - dan saya melakukannya, tetapi sistem tipe tidak dapat mencerminkan kenyataan dengan benar.

Intinya adalah tipe <strong i="7">@service</strong> class X { } yang benar di mana service dideklarasikan sebagai <T>(target: T) => T & IService bukan X , tetapi X & IService ; dan masalahnya adalah bahwa pada kenyataannya itu benar bahkan di dalam kelas - meskipun secara semantik itu tidak benar.

Masalah besar lainnya yang disebabkan oleh masalah ini adalah ketika Anda memiliki serangkaian dekorator yang masing-masing memiliki beberapa kendala, sistem tipe berpikir bahwa target selalu kelas asli, bukan kelas yang didekorasi, dan dengan demikian kendala tidak berguna.

Saya dapat mencapainya dengan warisan, tetapi warisan memaksa saya/pengguna saya untuk menggandakan kode secara besar-besaran, jadi saya ingin memiliki cara lain.

Ini adalah bagian yang saya tidak mengerti. Pengguna Anda perlu menerapkan IService , dan Anda ingin memastikan TheirService implements IService juga mematuhi beberapa kontrak lain { [P in keyof blablablah] } , dan mungkin Anda juga ingin mereka memiliki { potato: Potato } pada layanan mereka. Semua ini mudah dicapai tanpa anggota kelas mengetahui @service :

import { serviceDecorator, BaseService } from 'library';

// Right now
const MyService = serviceDecorator(class extends BaseService {
    async foo(): { return ""; }
})

const MyBrokenService1 = serviceDecorator(class extends BaseService {
    foo(): { return; } // Whoops, forgot async! Not assignable
});

const MyBrokenService2 = serviceDecorator(class { // Whoops, forgot to extend BaseService! Not assignable
    async foo(): { return; } 
});

// Once #4881 lands
<strong i="13">@serviceDecorator</strong>
class MyService extends BaseService {
    async foo(): { return ""; }
}

Dalam kedua kasus tersebut, tidak ada kemenangan besar dalam ringkasan yang hanya dapat dicapai dengan menghadirkan tipe pengembalian serviceDecorator sebagai tipe this ke foo . Lebih penting lagi, bagaimana Anda mengusulkan untuk menghasilkan strategi pengetikan yang baik untuk ini? Jenis pengembalian serviceDecorator disimpulkan berdasarkan jenis kelas yang Anda dekorasi, yang pada gilirannya sekarang diketik sebagai jenis pengembalian dekorator ...

Hai @masaeedu , keringkasan menjadi sangat berharga ketika Anda memiliki lebih dari satu.

@Component({ /** component args **/})
@Authorized({/** roles **/)
<strong i="7">@HasUndoContext</strong>
class MyComponent  {
  // do stuff with undo context, component methods etc
}

Namun solusi ini hanya merupakan alternatif untuk dekorator kelas. Untuk dekorator metode saat ini tidak ada solusi dan memblokir begitu banyak implementasi yang bagus. Dekorator sedang dalam proposal di tahap 2 - https://github.com/tc39/proposal-decorators . Namun ada banyak kasus implementasi dibuat jauh lebih awal. Saya pikir terutama dekorator adalah salah satu batu bata penting yang sangat penting karena sudah digunakan di banyak kerangka kerja dan versi yang sangat sederhana sudah diimplementasikan di babel/ts. Jika masalah ini dapat diimplementasikan, itu tidak akan kehilangan status eksperimentalnya hingga rilis resmi. Tapi itu "eksperimental" untuk.

@pietschy Ya, membuat gula sintaks @ berfungsi dengan baik dengan pemeriksaan tipe akan menyenangkan. Saat ini Anda dapat menggunakan komposisi fungsi untuk mendapatkan ringkasan yang cukup mirip:

const decorator = compose(
    Component({ /** component args **/ }),
    Authorized({ /** roles **/ })
    HasUndoContext
);

const MyComponent = decorator(class {
});

Diskusi sebelumnya adalah tentang apakah merupakan ide yang baik untuk melakukan semacam pengetikan terbalik di mana tipe pengembalian dekorator disajikan sebagai tipe this kepada anggota kelas.

Hai @masaeedu , ya saya mengerti konteks diskusi, karenanya // do stuff with undo context, component methods etc . Bersulang

benar-benar perlu membuat Mixin lebih mudah.

TypeScript (javascript) tidak mendukung multiple inheritance, jadi kita harus menggunakan Mixin atau Traits.
Dan sekarang Ini membuang-buang waktu saya, terutama ketika saya merekonstruksi sesuatu.
Dan saya harus menyalin & menempelkan antarmuka dengan "implementasi kosong" di mana-mana. (#371)

--
Saya tidak berpikir dekorator tipe kembali harus disajikan di kelas.
karena: .... emm. tidak tahu bagaimana menggambarkannya, maaf untuk kemampuan bahasa Inggris saya yang buruk ... ( dapatkah foto ada tanpa bingkai foto? ) pekerjaan ini untuk interface .

Akan menambahkan +1 saya ke yang ini! Saya ingin melihatnya segera.

@pietschy Jika kelas bergantung pada anggota yang ditambahkan oleh dekorator, maka itu harus memperluas hasil dekorator, bukan sebaliknya. Kamu seharusnya melakukan:

const decorator = compose(
    Component({ /** component args **/ }),
    Authorized({ /** roles **/ })
    HasUndoContext
);

class MyComponent extends decorator(class { }) {
    // do stuff with undo context, component methods etc
};

Alternatifnya adalah agar sistem tipe bekerja di beberapa jenis loop, di mana argumen fungsi dekorator diketik secara kontekstual oleh tipe pengembaliannya, yang disimpulkan dari argumennya, yang diketik secara kontekstual oleh tipe pengembaliannya, dll. Kita masih membutuhkan a proposal khusus tentang bagaimana ini seharusnya bekerja, tidak hanya menunggu implementasi.

Hai @masaeedu , Saya bingung mengapa saya harus membuat dekorator saya dan menerapkannya ke kelas dasar. Sejauh yang saya pahami, prototipe telah dimodifikasi pada saat kode tanah pengguna dijalankan. Misalnya

function pressable<T extends {new(...args:any[]):{}}>(constructor:T) {
    return class extends constructor {
        pressMe() {
            console.log('how depressing');
        }
    }
}

<strong i="7">@pressable</strong>
class UserLandClass {
    constructor() {    
        this['pressMe'](); // this method exists, please let me use code completion.
    }
}

console.log(new UserLandClass());

Jadi jika metode yang didefinisikan oleh dekorator sudah ada dan dapat dipanggil secara sah maka akan lebih baik jika TypeScript mencerminkan ini.

Keinginannya adalah agar TypeScript mencerminkan hal ini tanpa memaksakan solusi. Jika ada yang lain
hal-hal yang dapat dilakukan dekorator yang tidak dapat dimodelkan maka setidaknya akan lebih baik jika ini
skenario didukung dalam beberapa bentuk atau bentuk.

Utas ini penuh dengan pendapat tentang apa yang harus dilakukan dekorator, bagaimana seharusnya digunakan, bagaimana tidak boleh digunakan, dll ad mual .

Inilah yang sebenarnya dilakukan dekorator:

function addMethod(Class) : any {
    return class extends Class {
        hello(){}
    };
}

<strong i="13">@addMethod</strong>
class Foo{
    originalMethod(){}
}

yang menjadi, jika Anda menargetkan esnext

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};

function addMethod(Class) {
    return class extends Class {
        hello() {
        }
    };
}
let Foo = class Foo {
    originalMethod() { }
};
Foo = __decorate([
    addMethod
], Foo);

dengan __decorate menerapkan setiap dekorator dari bawah ke atas, dengan masing-masing memiliki opsi untuk mengembalikan kelas yang sama sekali baru.

Saya mengerti mendapatkan sistem tipe untuk mendukung dan mengakui ini mungkin sangat rumit, dan saya mengerti mungkin perlu waktu untuk mendukung ini, tetapi tidak bisakah kita semua setuju bahwa perilaku saat ini, dari contoh kode asli di atas yang memulai ini utas, apakah tidak benar?

Tidak peduli apa pendapat pengembang tentang dekorator atau mixin atau komposisi fungsional dll dll, ini tampaknya merupakan bug yang cukup jelas.


Dapatkah saya menanyakan dengan sopan apakah ini sedang dalam proses untuk rilis di masa mendatang?

TypeScript sangat menakjubkan dan saya menyukainya; namun ini sepertinya salah satu dari beberapa (hanya?) bagian yang rusak, dan saya hanya berharap untuk melihatnya diperbaiki :)

@arackaf Sudah dipahami dengan baik apa yang sebenarnya dilakukan dekorator, dan dengan asumsi Anda tidak peduli dengan tipe yang sudah mendukung dekorator TypeScript yang dipancarkan. Seluruh perdebatan dalam masalah ini adalah tentang bagaimana kelas yang didekorasi harus disajikan dalam sistem tipe.

Saya setuju bahwa new Foo().foo menjadi kesalahan tipe adalah bug, dan yang mudah diperbaiki pada saat itu. Saya tidak setuju bahwa return this.foo; menjadi kesalahan ketik adalah bug. Jika ada, Anda meminta fitur dalam sistem tipe yang sejauh ini belum ditentukan oleh siapa pun dalam masalah ini. Jika Anda memiliki beberapa mekanisme dalam pikiran di mana jenis this harus diubah oleh dekorator yang diterapkan ke kelas yang berisi, Anda perlu menyarankan mekanisme ini secara eksplisit .

Mekanisme seperti itu tidak sepele seperti yang Anda duga, karena dalam hampir semua kasus, tipe pengembalian dekorator disimpulkan dari tipe yang Anda usulkan untuk diubah menggunakan tipe pengembalian dekorator. Jika dekorator addMethod mengambil kelas tipe new () => T dan menghasilkan new() => T & { hello(): void } , tidak masuk akal untuk menyarankan bahwa T seharusnya T & { hello(): void } . Di dalam badan dekorator, apakah sah bagi saya untuk merujuk ke super.hello ?

Hal ini sangat penting karena saya tidak dibatasi untuk melakukan return class extends ClassIWasPassed { ... } dalam tubuh dekorator, saya dapat melakukan apapun yang saya suka; jenis pengurangan, jenis yang dipetakan, serikat pekerja, semuanya adalah permainan yang adil. Setiap jenis yang dihasilkan harus sesuai dengan loop inferensi yang Anda sarankan. Sebagai ilustrasi masalah, tolong beri tahu saya apa yang harus terjadi dalam contoh ini:

type Constructor<T> = new (...args: any[]) => T
type Greeter = { hello(): string }

function logger<T extends Constructor<Greeter>>(Class: T) {
    return class {
        hello() {
            console.log(new Class().hello()) // Do I get a type error here? After all, the signature of Class is { hello: () => void }
        } 
    };
}

// Or should I get the error here? Foo does not conform to { hello(): string }
<strong i="22">@logger</strong>
class Foo {
    hello() { return "foo"; }
}

Anda tidak bisa begitu saja mengabaikan masalah sebagai "rumit", seseorang harus benar-benar mengusulkan bagaimana ini seharusnya bekerja.

Bukan kesalahan - Anda memberikan Logger kelas yang sesuai dengan Greeter; namun Foo kemudian dimutasi oleh dekorator menjadi sesuatu yang sama sekali berbeda, yang tidak lagi meluas ke Greeter. Jadi seharusnya valid bagi dekorator logger untuk memperlakukan Foo sebagai Penyambut, tetapi tidak valid bagi orang lain, karena Foo ditugaskan kembali ke sesuatu yang sama sekali berbeda oleh dekorator.

Saya yakin menerapkan ini akan sangat sulit. Apakah beberapa subset yang masuk akal mungkin, untuk kasus umum seperti yang tercantum di bagian atas utas ini, dengan solusi seperti penggabungan antarmuka menjadi fallback untuk kasus tepi yang rumit seperti ini?

@arackaf

namun Foo kemudian dimutasi oleh dekorator menjadi sesuatu yang sama sekali berbeda

Tidak ada definisi yang tepat untuk apa Foo itu. Ingat bahwa seluruh titik pertikaian kita adalah apakah anggota Foo harus dapat mengakses (dan karenanya kembali sebagai bagian dari API publik), dekorator memperkenalkan anggota dari this . Apa Foo ditentukan oleh apa yang dikembalikan dekorator, dan apa yang dikembalikan dekorator ditentukan oleh apa Foo itu. Mereka tidak terpisahkan.

Saya yakin menerapkan ini akan sangat sulit

Masih terlalu dini untuk berbicara tentang kompleksitas implementasi, ketika kami bahkan tidak memiliki proposal yang solid tentang bagaimana fitur tersebut harus bekerja. Mohon maaf untuk hal ini, tetapi kami benar- benar membutuhkan seseorang untuk mengusulkan mekanisme konkret untuk "apa yang terjadi pada this ketika saya menerapkan urutan dekorator seperti itu ke kelas seperti itu". Kami kemudian dapat memasukkan berbagai konsep TypeScript di bagian yang berbeda dan melihat apakah yang keluar masuk akal. Baru kemudian masuk akal untuk membahas kompleksitas implementasi.

Apakah keluaran dekorator saat ini yang saya tempel di atas tidak sesuai dengan spesifikasi? Saya berasumsi itu dari apa yang saya lihat.

Dengan asumsi itu, Foo memiliki arti yang cukup tepat di setiap langkahnya. Saya berharap TS mengizinkan saya untuk menggunakan this (Di dalam metode Foo dan pada contoh Foo) berdasarkan apa yang dikembalikan dekorator terakhir, dengan TS memaksa saya untuk menambahkan anotasi jenis di sepanjang jalan sesuai kebutuhan jika dekorator fungsi tidak cukup dapat dianalisis.

@arackaf Tidak ada "langkah-langkah"; kami hanya memperhatikan jenis akhir this untuk potongan kode yang tidak dapat diubah. Anda perlu menjelaskan, setidaknya pada tingkat tinggi, apa yang Anda harapkan dari tipe this untuk anggota kelas X didekorasi dengan fungsi dekorator f1...fn , dalam istilah dari jenis tanda tangan X dan f1...fn . Anda dapat memiliki banyak langkah dalam proses yang Anda inginkan. Sejauh ini, belum ada yang melakukan ini. Saya telah menebak bahwa maksud orang-orang bahwa tipe pengembalian dekorator harus disajikan sebagai tipe this , tetapi untuk semua yang saya tahu saya bisa benar-benar melenceng.

Jika proposal Anda adalah untuk secara mekanis membuat jenis mencerminkan apa yang terjadi pada nilai-nilai dalam output yang diubah, Anda berakhir dengan apa yang saya usulkan alih-alih apa yang Anda usulkan: yaitu new Foo().hello() baik-baik saja, tetapi this.hello() tidak. Dalam contoh tersebut, kelas asli yang Anda dekorasi tidak mendapatkan metode hello . Hanya hasil dari __decorate([addMethod], Foo) (yang kemudian ditetapkan ke Foo ) yang memiliki metode hello .

Saya sudah menduga bahwa orang-orang berarti tipe kembalinya dekorator harus disajikan sebagai tipe ini

Oh, maaf, ya, itu benar. Tepat itu. Titik. Karena itulah yang dilakukan para dekorator. Jika dekorator terakhir dalam antrean mengembalikan beberapa kelas yang sama sekali baru, maka itulah Foo.

Dengan kata lain:

<strong i="9">@c</strong>
<strong i="10">@b</strong>
<strong i="11">@a</strong>
class Foo { 
}

Foo adalah apa pun di dunia yang dikatakan c . Jika c adalah fungsi yang mengembalikan any maka, saya tidak tahu - mungkin hanya mundur ke Foo asli? Itu sepertinya pendekatan yang masuk akal dan kompatibel ke belakang.

tetapi jika c mengembalikan beberapa tipe baru X , maka saya benar-benar berharap Foo menghormatinya.

Apakah saya melewatkan sesuatu?


memperjelas lebih lanjut, jika

class X { 
    hello() {}
    world() {}
}
function c(cl : any) : X {  // or should it be typeof X ?????
    //...
}

<strong i="25">@c</strong>
<strong i="26">@b</strong>
<strong i="27">@a</strong>
class Foo { 
    sorry() {}
}

new Foo().hello(); //perfectly valid
new Foo().sorry(); //ERROR 

Apakah saya melewatkan sesuatu?

@arackaf Ya, apa yang kurang dari pendekatan naif ini adalah bahwa dekorator bebas mengembalikan tipe arbitrer, tanpa batasan bahwa hasilnya kompatibel dengan tipe Foo .

Ada sejumlah absurditas yang bisa Anda hasilkan dengan ini. Katakanlah saya menangguhkan keberatan saya tentang kebulatan pengetikan this sebagai hasil dari c , yang ditentukan oleh jenis this , yang ditentukan oleh hasil c , dll. Ini masih tidak berhasil. Berikut contoh lain yang tidak masuk akal:

type Constructor<T> = new (...args: any[]) => T
type Greeter = { hello(): string }

function logger<T extends Constructor<Greeter>>(Class: T) {
    return class {
        hello() {
            console.log(new Class().hello())
        }
    };
}


<strong i="15">@logger</strong>
class Foo {
    foo() { return "bar" }
    // Whoops, `this` is `{ hello(): void }`, it has no `foo` method
    hello() { return this.foo(); }
}

Untuk kasus ini, Anda terpaksa menghasilkan kesalahan untuk kode yang benar-benar tidak berbahaya, atau menyesuaikan proposisi bahwa this sama persis dengan tipe pengembalian dekorator.

Saya tidak yakin saya melihat masalahnya. @logger telah memilih untuk mengembalikan kelas baru, tanpa metode foo , dan dengan metode hello() yang benar-benar baru, yang kebetulan merujuk kembali ke yang asli, sekarang tidak dapat dijangkau Foo .

new Foo().foo()

tidak berlaku lagi; itu akan menghasilkan kesalahan runtime. Saya hanya mengatakan itu juga harus menghasilkan kesalahan waktu kompilasi.

Yang mengatakan, jika menganalisis semua itu secara statis terlalu sulit, akan sangat masuk akal untuk memaksa kami menambahkan anotasi tipe eksplisit ke logger untuk menandakan dengan tepat apa yang sedang dikembalikan. Dan jika tidak ada anotasi jenis seperti itu, maka saya akan mengatakan kembali ke asumsi Foo dikembalikan kembali. Itu harus membuatnya tetap kompatibel.

@arackaf Tidak ada masalah dengan kode dalam hal pengetikan atau evaluasi runtime. Saya dapat memanggil new Foo().hello() , yang secara internal akan memanggil hello kelas yang didekorasi, yang akan memanggil bar kelas yang didekorasi. Bukan kesalahan bagi saya untuk memanggil bar di dalam kelas asli.

Anda dapat mencobanya sendiri dengan menjalankan contoh lengkap ini di taman bermain:

// Code from previous snippet...

const LoggerFoo = logger(Foo)
new LoggerFoo().hello()

Tentu - tetapi saya mengatakan itu adalah kesalahan untuk dipanggil

new Foo().foo()

Tidak ada masalah dengan kode dalam hal pengetikan atau evaluasi runtime. Saya dapat memanggil new Foo().hello(), yang secara internal akan memanggil hello kelas yang didekorasi, yang akan memanggil bilah kelas yang didekorasi

Tapi itu harus menjadi kesalahan untuk mengatakan

let s : string = new Foo().hello();

karena metode hello Foo sekarang mengembalikan void, per kelas yang dikembalikan Logger.

Tentu - tapi saya bilang itu adalah kesalahan untuk memanggil new Foo().foo()

@arackaf Tapi itu tidak masalah. Saya tidak memanggil new Foo().foo() . Saya memanggil this.foo() , dan saya mendapatkan kesalahan ketik, meskipun kode saya berfungsi dengan baik saat runtime.

Tapi seharusnya salah untuk mengatakan let s : string = new Foo().hello()

Sekali lagi, ini tidak relevan. Saya tidak mengatakan tipe terakhir dari Foo.prototype.hello harus () => string (Saya setuju seharusnya () => void ). Saya mengeluh tentang kesalahan pemanggilan this.bar() yang valid, karena Anda telah mentransplantasikan jenis yang tidak masuk akal untuk ditransplantasikan.

Ada dua Foo di sini. Ketika kamu mengatakan

class Foo { 
}

Foo adalah pengikatan yang tidak dapat diubah DI DALAM kelas, dan pengikatan yang dapat diubah di luar kelas. Jadi ini berfungsi dengan baik karena Anda dapat memverifikasi di jsbin

class Foo { 
  static sMethod(){
    alert('works');
  }
  hello(){ 
    Foo.sMethod();
  }
}

let F = Foo;

Foo = null;

new F().hello();

Contoh Anda di atas melakukan hal serupa; itu menangkap referensi ke kelas asli sebelum pengikatan luar itu bermutasi. Saya masih tidak yakin apa yang Anda kendarai.

this.foo(); benar-benar valid, dan saya tidak mengharapkan kesalahan ketik (saya juga tidak akan menyalahkan orang-orang TS jika saya memerlukan referensi apa pun, karena saya yakin menelusuri ini akan sulit)

this.foo(); benar-benar valid, dan saya tidak mengharapkan kesalahan ketik

Bagus, jadi kami setuju, tetapi ini berarti Anda sekarang harus memenuhi syarat atau menolak proposal bahwa this menjadi jenis instans dari apa pun yang dikembalikan oleh dekorator. Jika menurut Anda itu bukan kesalahan ketik, apa yang seharusnya menjadi this daripada { hello(): void } dalam contoh saya?

this tergantung pada apa yang dipakai.

<strong i="7">@c</strong>
class Foo{
}

new Foo(). // <---- this is based on whatever c returned 

function c(Cl){
    new Cl().  // <----- this is an object whose prototype is the original Foo's prototype
                   // but for TS's purpose, for type errors, it'd depend on how Cl is typed
}

Bisakah kita tetap berpegang pada satu contoh konkret? Itu akan membuat segalanya lebih mudah bagi saya untuk mengerti. Dalam cuplikan berikut:

type Constructor<T> = new (...args: any[]) => T
type Greeter = { hello(): string }

function logger<T extends Constructor<Greeter>>(Class: T) {
    return class {
        hello() {
            console.log(new Class().hello())
        }
    };
}

<strong i="6">@logger</strong>
class Foo {
    foo() { return "bar" }
    hello() { return this.foo(); } /// <------
}

Apa jenis this ? Jika { hello(): void } , maka saya akan mendapatkan kesalahan ketik, karena foo bukan anggota { hello(): void } . Jika bukan { hello(): void } , maka this bukan hanya tipe instans dari tipe pengembalian dekorator, dan Anda perlu menjelaskan logika alternatif apa pun yang Anda gunakan untuk sampai pada tipe this .

EDIT: Lupa menambahkan dekorator pada Foo . Tetap.

this , di mana Anda memiliki panah, tentu saja merupakan turunan dari Foo asli. Tidak ada kesalahan ketik.

Ah - saya mengerti maksud Anda sekarang; tapi saya masih tidak melihat di mana masalahnya. this.foo() DALAM Foo asli bukan kesalahan tipe - yang valid untuk kelas (sekarang tidak dapat dijangkau) yang dulu terikat dengan pengidentifikasi Foo .

Ini adalah hal-hal sepele yang aneh dan menyenangkan, tetapi saya tidak mengerti mengapa ini harus mencegah TS menangani dekorator kelas yang bermutasi.

@arackaf Anda tidak menjawab pertanyaan. Apa, khususnya, jenis this ? Anda tidak bisa begitu saja tanpa henti menjawab " this adalah Foo dan Foo adalah this ". Anggota apa yang dimiliki this ? Jika memiliki anggota selain hello(): void , logika apa yang digunakan untuk menentukannya?

Ketika Anda mengatakan " this.foo() DALAM Foo asli bukan kesalahan tipe", Anda masih perlu menjawab pertanyaan: apa tipe struktural this sehingga itu bukan kesalahan tipe untuk lakukan this.foo() ?

Juga, kelas asli tidak "tidak terjangkau". Setiap fungsi yang ditentukan dalam cuplikan kode tersebut dijalankan secara aktif saat runtime, dan semuanya bekerja tanpa hambatan. Silakan jalankan tautan taman bermain yang saya berikan dan lihat konsol. Dekorator mengembalikan kelas baru di mana metode hello mendelegasikan ke metode hello dari kelas yang didekorasi, yang pada gilirannya mendelegasikan ke metode foo dari kelas yang didekorasi.

Ini adalah hal-hal sepele yang aneh dan menyenangkan, tetapi saya tidak mengerti mengapa ini harus mencegah TS menangani dekorator kelas yang bermutasi.

Tidak ada "trivia" dalam sistem tipe. Anda tidak akan mendapatkan kesalahan ketik "anak nakal, Anda tidak bisa melakukan itu" TSC-1234 karena kasus penggunaan terlalu khusus. Jika suatu fitur menyebabkan kode yang sangat normal rusak dengan cara yang mengejutkan, fitur tersebut perlu dipikirkan ulang.

Anda tidak akan mendapatkan kesalahan ketik "anak nakal, Anda tidak bisa melakukan itu" TSC-1234 karena kasus penggunaan terlalu khusus.

Persis itulah yang saya dapatkan ketika saya mencoba menggunakan metode yang ditambahkan ke definisi kelas oleh dekorator. Saat ini saya harus mengatasinya dengan menambahkan definisi ke kelas, atau menggunakan penggabungan antarmuka, casting sebagai any dll.

Saya telah menjawab setiap pertanyaan tentang apa itu this , dan di mana.

Fakta sederhana dari masalah ini adalah bahwa arti dari this berubah berdasarkan di mana Anda berada.

type Constructor<T> = new (...args: any[]) => T
type Greeter = { hello(): string }

function logger<T extends Constructor<Greeter>>(Class: T) {
    return class {
        hello() {
            console.log(new Class().hello())
        }
    };
}

class Foo {
    foo() { return "bar" }
    hello() { return this.foo(); } /// <------
}

const LoggableFoo = logger(Foo)
new LoggableFoo().hello() // Logs "bar"

ketika Anda mengatakan new Class() - Kelas menunjuk ke Foo asli, dan dari perspektif TypeScript itu akan memiliki akses ke hello(): string karena itulah yang diketik sebagai Kelas (memperpanjang Penyambut). Saat runtime Anda akan membuat instance dari Foo asli.

new LoggableFoo().hello() kebetulan memanggil metode kosong yang kebetulan memanggil metode yang tidak dapat dijangkau, diketik melalui Greeter.

Jika Anda sudah selesai

<strong i="21">@logger</strong>
class Foo {
    foo() { return "bar" }
    hello() { return this.foo(); }
}

maka Foo sekarang menjadi kelas dengan hanya hello(): void dan new Foo().foo() harus menjadi kesalahan Type.

Dan, sekali lagi, hello() { return this.foo(); } bukan TypeError - mengapa demikian? Hanya karena definisi kelas itu tidak lagi dapat dijangkau tidak membuat metode itu tidak valid.

Saya tidak berharap TypeScript mendapatkan semua kasus tepi ini dengan sempurna; itu akan cukup dimengerti harus menambahkan anotasi jenis di sana-sini, seperti biasa. Tetapi tidak satu pun dari contoh ini yang menunjukkan mengapa @logger tidak dapat secara mendasar mengubah apa yang terikat pada Foo .

Jika logger adalah fungsi yang mengembalikan kelas baru, maka ITU adalah referensi Foo sekarang.

Saya telah menjawab setiap pertanyaan tentang apa ini, dan di mana.
Fakta sederhana dari masalah ini adalah bahwa arti dari this berubah berdasarkan di mana Anda berada.

Ini benar-benar membuat frustrasi. Baik, itu berubah, itu Foo , itu adalah pengikatan statis, dll. dll. Apa jenis tanda tangan? Anggota apa yang dimiliki this ? Anda berbicara tentang segala sesuatu di bawah matahari, ketika yang saya butuhkan dari Anda adalah tanda tangan tipe sederhana untuk apa this ada di dalam hello .

new LoggableFoo().hello() kebetulan memanggil metode batal yang kebetulan memanggil metode yang tidak dapat dijangkau, diketik melalui Greeter.

Ini tidak sama dengan tidak terjangkau. Setiap metode yang dapat dijangkau adalah "jika tidak dapat dijangkau" ketika Anda mengabaikan jalur yang dapat dijangkau.

Jika Anda telah melakukan:

Tapi ini benar- benar apa yang telah saya lakukan. Itu persis potongan kode saya, ditempel lagi, diawali dengan penjelasan tentang contoh yang saya buat untuk Anda. Saya bahkan menjalankannya melalui pemeriksa perbedaan untuk memastikan saya tidak meminum pil gila, dan satu-satunya perbedaan adalah komentar yang Anda hapus.

Dan, sekali lagi, hello() { return this.foo(); } bukan TypeError - mengapa demikian?

Karena Anda (dan orang lain di sini) ingin tipe this menjadi tipe pengembalian yang dipakai dari dekorator, yaitu { hello(): void } (perhatikan tidak adanya anggota foo ). Jika Anda ingin anggota Foo dapat melihat this sebagai tipe pengembalian dekorator, tipe this di dalam hello akan menjadi { hello(): void } . Jika { hello(): void } , saya mendapatkan kesalahan ketik. Jika saya mendapatkan kesalahan ketik, saya sedih, karena kode saya berjalan dengan baik.

Jika Anda mengatakan itu bukan kesalahan tipe, Anda mengabaikan skema Anda sendiri untuk memasok tipe this melalui tipe pengembalian dekorator. Jenis this kemudian { hello(): string; bar(): string } , terlepas dari apa yang dikembalikan oleh dekorator. Anda mungkin memiliki beberapa skema alternatif untuk menghasilkan jenis this yang menghindari masalah ini, tetapi Anda perlu menentukan apa itu.

Anda tampaknya tidak mengerti bahwa, setelah dekorator berjalan, Foo dapat mereferensikan sesuatu yang benar-benar terpisah dari definisi aslinya.

function a(C){
    return class {
        x(){}
        y(){}
        z(){}
    }
}

<strong i="7">@a</strong>
class Foo {
    a(){ 
        this.b();  //valid
    }
    b() { 
        this.c();  //also valid
    }
    c(){ 
        return 0;
    }
}

let f = new Foo();
f.x(); //valid
f.y(); //also valid
f.z(); //still valid

Saya menduga Anda menemukan beberapa keanehan dalam this menjadi sesuatu yang berbeda di dalam Foo di atas, daripada dalam kasus yang kemudian dibuat dari apa Foo akhirnya (setelah dekorator berjalan)?

Saya tidak yakin apa yang harus saya katakan kepada Anda; begitulah cara kerja dekorator. Satu-satunya argumen saya adalah bahwa TypeScript harus lebih cocok dengan apa yang terjadi.

Dengan kata lain, jenis tanda tangan di dalam Foo (asli) akan berbeda dari apa yang Foo/hasilkan setelah dekorator berjalan.

Untuk meminjam analogi dari bahasa lain, di dalam Foo yang didekorasi ini akan merujuk pada yang setara dengan tipe anonim C#—tipe yang benar-benar nyata, yang dinyatakan valid, hanya saja tidak benar-benar dapat direferensikan secara langsung. Akan terlihat aneh mendapatkan kesalahan dan non-kesalahan yang dijelaskan di atas, tetapi sekali lagi, begitulah cara kerjanya. Dekorator memberi kita kekuatan luar biasa untuk melakukan hal-hal aneh seperti ini.

Saya menduga Anda menemukan beberapa keanehan dalam hal ini menjadi sesuatu yang berbeda di dalam Foo di atas, daripada dalam kasus yang kemudian dibuat dari apa Foo akhirnya (setelah dekorator berjalan)?

Tidak. Saya tidak menemukan keanehan dalam hal itu, karena persis seperti yang saya usulkan 200 komentar yang lalu. Apakah Anda bahkan membaca salah satu diskusi sebelumnya?

Cuplikan yang Anda posting sama sekali tidak kontroversial. Orang-orang yang tidak saya setujui, dan yang bantuannya Anda lompati juga menginginkan yang berikut ini:

function a(C){
    return class {
        x(){}
        y(){}
        z(){}
    }
}

<strong i="10">@a</strong>
class Foo {
    a(){ 
        this.b();  //valid
    }
    b() { 
        this.c();  //also valid
    }
    c(){ 
        return 0;
    }
    d(){
        // HERE: All of these are also expected to be valid
        this.x();
        this.y();
        this.z();
    }
}

let f = new Foo();
f.x(); //valid
f.y(); //also valid
f.z(); //still valid

Saya tidak setuju bahwa adalah mungkin untuk melakukan ini dengan baik, dan telah mati-matian mencoba untuk mencari tahu bagaimana saran seperti itu akan bekerja. Terlepas dari upaya terbaik saya, saya tidak dapat mengekstrak daftar lengkap anggota yang diharapkan dimiliki this , atau bagaimana daftar ini akan dibangun di bawah proposal.

Dengan kata lain, jenis tanda tangan di dalam Foo (asli) akan berbeda dari apa yang Foo/hasilkan setelah dekorator berjalan.

Jadi mengapa Anda bahkan berdebat dengan saya? Saya berkata: "Saya setuju bahwa new Foo().foo menjadi kesalahan tipe adalah bug, dan yang mudah diperbaiki pada saat itu. Saya tidak setuju bahwa mengembalikan this.foo; menjadi kesalahan tipe adalah bug". Secara analog, dalam contoh Anda, saya setuju bahwa new Foo().x() menjadi kesalahan tipe adalah bug, tetapi this.x() menjadi kesalahan tipe tidak.

Anda lihat bagaimana ada dua komentar di cuplikan di bagian atas halaman ini?

        return this.foo; // Property 'foo' does not exist on type 'Foo'

^ Yang itu menurut saya bermasalah. Entah Anda setuju bahwa tipe pengembalian dekorator tidak boleh ditampilkan pada this , dan hanya pada new Foo() , dalam hal ini kami berdua tidak berdebat tentang apa pun. Atau Anda tidak setuju dan menginginkan fitur itu juga, dalam hal ini cuplikan di komentar Anda sebelumnya tidak relevan.

Saya akhirnya mengerti maksud Anda. Sangat sulit bagi saya untuk mendapatkan ini dari kode Penyambut Anda, tetapi saya sedang melacak sekarang; terima kasih sudah begitu sabar.

Saya akan mengatakan satu-satunya solusi yang masuk akal adalah untuk Foo ( di dalam Foo) untuk mendukung tipe union - derp maksud saya persimpangan - dari Foo asli, dan apa pun yang dikembalikan oleh dekorator terakhir. Anda harus mendukung Foo asli untuk contoh gila seperti Penyambut Anda, dan Anda pasti perlu mendukung apa pun yang dikembalikan dekorator terakhir karena itulah inti dari penggunaan dekorator (sesuai banyak komentar di atas).

Jadi ya, dari contoh terbaru saya, di dalam Foo x, y, z, a, b, c semuanya akan berfungsi. Jika ada dua versi a , maka dukung keduanya.

@arackaf np, terima kasih atas kesabaran Anda juga. Contoh saya belum yang paling jelas karena saya hanya memposting apa pun yang saya bisa masak di taman bermain yang menunjukkan bagaimana itu rusak. Sulit bagi saya untuk memikirkannya secara sistematis.

Saya akan mengatakan satu-satunya solusi yang masuk akal adalah Foo (di dalam Foo) untuk mendukung penyatuan tipe Foo asli, dan apa pun yang dikembalikan oleh dekorator terakhir.

Ok luar biasa, jadi kita membahas secara spesifik sekarang. Perbaiki saya jika saya salah, tetapi ketika Anda mengatakan "union" maksud Anda itu harus memiliki tipe anggota dari keduanya, yaitu harus A & B . Jadi kami ingin this menjadi typeof(new OriginalClass()) & typeof(new (decorators(OriginalClass))) , di mana decorators adalah tanda tangan tipe tersusun dari semua dekorator . Dalam bahasa Inggris biasa, kami ingin this menjadi perpotongan dari tipe instantiated dari "original class" dan tipe instantiated dari "original class" melewati semua dekorator.

Ada dua masalah dengan ini. Salah satunya adalah dalam kasus seperti contoh saya, ini hanya memungkinkan Anda untuk mengakses anggota yang tidak ada. Saya dapat menambahkan banyak anggota di dekorator, tetapi jika saya mencoba mengaksesnya di kelas saya menggunakan this.newMethod() , itu hanya akan muntah saat runtime. newMethod hanya ditambahkan ke kelas yang dikembalikan dari fungsi dekorator, anggota kelas asli tidak memiliki akses ke sana (kecuali saya secara khusus menggunakan pola return class extends OriginalClass { newMethod() { } } ).

Masalah lainnya adalah bahwa "kelas asli" bukanlah konsep yang terdefinisi dengan baik. Jika saya dapat mengakses anggota yang didekorasi dari this , saya juga dapat menggunakannya sebagai bagian dari pernyataan pengembalian, dan karenanya mereka dapat menjadi bagian dari API publik "kelas asli". Saya agak lamban di sini, dan saya agak terlalu lelah untuk memikirkan contoh-contoh konkret, tetapi saya pikir jika kita berpikir keras tentang hal itu, kita dapat menemukan contoh-contoh yang tidak masuk akal. Mungkin Anda dapat mengatasi ini dengan menemukan beberapa cara untuk memisahkan anggota yang tidak mengembalikan hal-hal yang mereka akses dari this atau setidaknya yang tipe pengembaliannya tidak disimpulkan sebagai akibat dari pengembalian this.something() .

@masaeedu ya, saya mengoreksi diri saya pada hal serikat / persimpangan sebelum balasan Anda. Ini kontra-intuitif bagi seseorang yang baru mengenal TS.

Dipahami sisanya. Jujur dekorator biasanya tidak akan mengembalikan tipe yang sama sekali berbeda, mereka biasanya hanya akan menambah tipe yang diteruskan, sehingga hal persimpangan akan "hanya bekerja" dengan aman sebagian besar waktu.

Saya akan mengatakan kesalahan runtime yang Anda bicarakan akan jarang terjadi, dan hasil dari beberapa keputusan pengembangan yang sengaja dibuat buruk. Saya tidak yakin Anda harus benar-benar peduli tentang ini, tetapi, jika itu benar-benar masalah, saya akan mengatakan hanya menggunakan apa yang dikembalikan dekorator terakhir akan menjadi tempat kedua yang layak (jadi ya, kelas dapat melihat TypeError oleh mencoba menggunakan metode yang didefinisikan sendiri - tidak ideal, tetapi masih merupakan harga yang pantas untuk dibayar agar dekorator bekerja).

Tapi sungguh, saya pikir kesalahan runtime yang Anda pikirkan tidak layak untuk dicegah, tentu saja dengan mengorbankan dekorator yang bekerja dengan benar. Selain itu, cukup mudah untuk menghasilkan kesalahan runtime di TS jika Anda ceroboh atau konyol.

interface C { a(); }
class C {
    foo() {
        this.a();  //<--- boom
    }
}

let c = new C();
c.foo();

Mengenai keberatan keduamu

Saya juga dapat menggunakannya sebagai bagian dari pernyataan pengembalian, dan karenanya dapat menjadi bagian dari API publik "kelas asli"

Saya khawatir saya tidak melihat ada masalah dengan itu. Saya ingin apa pun yang ditambahkan ke kelas melalui dekorator benar-benar menjadi warga negara kelas satu. Saya ingin tahu apa potensi masalah yang akan terjadi.

Saya pikir satu pilihan bagus lainnya adalah hanya mengimplementasikan ini sebagian.

Saat ini, kelas selalu diketik sesuai definisinya. Jadi di dalam Foo, ini didasarkan pada bagaimanapun foo didefinisikan, terlepas dari dekoratornya. Ini akan menjadi peningkatan yang sangat besar untuk "hanya" memperluasnya untuk beberapa kasus penggunaan dekorator yang berguna (yaitu yang paling umum)

Bagaimana jika Anda mengizinkan kelas diperluas (dari perspektif this di dalam kelas) jika dan hanya jika dekorator mengembalikan sesuatu yang memperluas aslinya, yaitu untuk

function d(Class) {
    return class extends Class {
        blah() { }
    };
}

<strong i="9">@d</strong>
class Foo {
    a() { }
    b() { }
    c() { 
        this.blah(); // <---- valid
    }
}

Kerjakan saja, dan berikan dukungan kelas satu untuk bla dan apa pun yang ditambahkan oleh dekorator. Dan untuk kasus penggunaan yang melakukan hal-hal gila seperti mengembalikan kelas yang sama sekali baru (seperti Penyambut Anda), lanjutkan saja perilaku saat ini, dan abaikan apa yang dilakukan dekorator.


Btw, terlepas dari apa yang Anda pilih, bagaimana saya akan membubuhi keterangan itu? Bisakah ini dijelaskan saat ini? Saya mencoba

function d<T>(Class: new() => T): T & { new (): { blah(): void } } {
    return class extends Class {
        blah() { }
    };
}

serta banyak variasi pada tema itu, tetapi TypeScript tidak memilikinya :)

@arackaf Dekorator hanyalah sebuah fungsi. Dalam kasus umum, Anda akan mendapatkannya dari file .d.ts di suatu tempat, dan tidak tahu bagaimana penerapannya. Anda tidak tahu apakah implementasinya mengembalikan kelas yang sama sekali baru, menambah/mengurangi/mengganti anggota pada prototipe kelas asli dan mengembalikannya, atau memperluas kelas asli. Yang Anda miliki hanyalah tipe pengembalian struktural dari fungsi tersebut.

Jika Anda ingin entah bagaimana mengikat dekorator ke pewarisan kelas, Anda harus mengajukan konsep bahasa terpisah untuk JS terlebih dahulu. Cara kerja dekorator hari ini tidak membenarkan mutasi this dalam kasus umum. Sebagai contoh, saya pribadi selalu lebih suka komposisi daripada pewarisan, dan akan selalu melakukan:

function logger<T extends Constructor<Greeter>>(Class: T) {
    return class {
        readonly _impl;
        constructor() {
            this._impl = new Class()
        }
        // Use _impl ...
    };
}

Ini bukan eksperimen akademis yang gila, ini adalah pendekatan standar yang buruk untuk melakukan mixin, dan ini melanggar contoh yang saya berikan kepada Anda. Sebenarnya hampir semua hal kecuali return class extends Class putus dengan contoh yang saya berikan kepada Anda, dan dalam banyak kasus banyak hal akan mencapai titik impas dengan return class extends Class .

Anda harus melewati semua jenis rintangan dan liuk untuk membuat ini berhasil, dan sistem tipe akan melawan Anda di setiap langkah, karena apa yang Anda lakukan tidak masuk akal dalam kasus umum. Lebih penting lagi, setelah Anda mengimplementasikannya, setiap orang harus melakukan manuver akrobatik di sekitar sudut sistem tipe yang tidak masuk akal ini setiap kali mereka mencoba menerapkan beberapa konsep kompleks (tetapi terdengar) lainnya.

Hanya karena Anda memiliki satu kasus penggunaan yang Anda rasa penting (dan saya telah mencoba beberapa kali di utas ini untuk menunjukkan cara alternatif untuk mengekspresikan apa yang Anda inginkan dengan baik dalam sistem tipe yang ada), tidak berarti bahwa satu-satunya hal yang benar lakukan adalah terus maju dengan pendekatan yang Anda sarankan dengan cara apa pun. Anda dapat menggabungkan antarmuka arbitrer ke kelas Anda, termasuk tipe kembalinya fungsi dekorator, jadi bukannya tidak mungkin bagi Anda untuk pergi ke mana pun jika Anda bersikeras menggunakan dekorator seperti yang Anda gunakan.

@arackaf ini:

function d<T>(Class: new() => T): T & { new (): { blah(): void } } {
    return class extends Class {
        blah() { }
    };
}

Harus berfungsi dengan baik di klausa yang diperluas:

class C extends d(S) {
  foo() {
    this.blah(); // tsc is happy here
  }
}

Dengan semantik yang jauh lebih mudah dan sudah ditentukan dalam sistem tipe. Ini sudah bekerja. Masalah apa yang Anda coba selesaikan dengan mensubklasifikasikan kelas yang dideklarasikan, daripada membuat superkelas untuknya?

@masaeedu

Cara dekorator bekerja hari ini tidak membenarkan mutasi ini dalam kasus umum.

Penggunaan utama untuk dekorator (kelas) mutlak, positif untuk mengubah nilai this di dalam kelas, dengan satu atau lain cara. @connect @observer MobX mengambil kelas, dan mengeluarkan versi BARU dari kelas asli, dengan fitur tambahan. Dalam kasus-kasus tertentu saya tidak berpikir struktur sebenarnya dari this berubah (hanya struktur this.props ), tapi itu tidak penting.

Ini adalah kasus penggunaan yang cukup umum (seperti yang Anda lihat dari komentar di atas) untuk menggunakan dekorator untuk mengubah kelas entah bagaimana). Untuk orang yang lebih baik atau lebih buruk cenderung tidak menyukai alternatif sintaksis

let Foo = a(b(c(
    class Foo {
    }
})));

sebagai lawan

<strong i="19">@a</strong>
<strong i="20">@b</strong>
<strong i="21">@c</strong>
class Foo {
}

Sekarang, apakah seorang dekorator melakukannya

function d (Class){
    Class.prototype.blah = function(){
    };
}

atau

function d(Class){
    return class extends Class {
        blah(){ }
    }
}

seharusnya tidak masalah. Satu atau lain cara, kasus penggunaan yang benar-benar diteriakkan oleh banyak orang, adalah untuk dapat memberi tahu TypeScript, dengan anotasi apa pun yang diperlukan, tidak peduli seberapa tidak nyamannya bagi kami, bahwa function c mengambil kelas C di, dan mengembalikan kelas yang memiliki struktur C & {blah(): void} .

Itulah yang banyak dari kita secara aktif menggunakan dekorator untuk hari ini. Kami benar-benar ingin mengintegrasikan subset yang berguna dari perilaku ini ke dalam sistem tipe.

Sangat mudah ditunjukkan bahwa dekorator dapat melakukan hal-hal aneh yang tidak mungkin dilacak oleh sistem tipe. Bagus! Tapi pasti ada cara untuk menjelaskan itu

<strong i="38">@c</strong>
class Foo {
    hi(){ this.addedByC(); }
}

adalah benar. Saya tidak tahu apakah itu akan memerlukan sintaks anotasi tipe baru untuk c , atau jika sintaks anotasi tipe yang ada dapat ditekan ke layanan pada c untuk mencapai ini, tetapi hanya memperbaiki penggunaan umum case (dan membiarkan ujungnya apa adanya, tanpa efek yang terjadi pada kelas asli) akan menjadi keuntungan besar bagi pengguna TS.

@justinfagnani fyi ini

function d<T>(Class: new() => T): T & { new (): { blah(): void } } {
    return class extends Class {
        blah() { }
    };
}

menghasilkan

Ketik 'typeof (Anonim class)' tidak dapat ditetapkan untuk mengetik 'T & (baru () => { blah(): void; })'.
Ketik 'typeof (Kelas anonim)' tidak dapat ditetapkan untuk mengetik 'T'.

di taman bermain TS. Tidak yakin apakah saya memiliki beberapa opsi yang salah.

Masalah apa yang Anda coba selesaikan dengan mensubklasifikasikan kelas yang dideklarasikan, daripada membuat superkelas untuknya?

Saya hanya mencoba menggunakan dekorator. Saya dengan senang hati menggunakannya di JS selama lebih dari setahun - saya hanya ingin TS mengejar ketinggalan. Dan saya tentu saja tidak menikah dengan subclassing. Terkadang dekorator hanya mengubah prototipe kelas asli, untuk menambahkan properti atau metode. Bagaimanapun, tujuannya adalah untuk memberi tahu TypeScript bahwa

<strong i="17">@c</strong>
class Foo { 
}

akan menghasilkan kelas dengan anggota baru, dibuat oleh c .

@arackaf Kami mulai tidak sinkron lagi. Perdebatannya bukan tentang ini:

<strong i="8">@a</strong>
<strong i="9">@b</strong>
<strong i="10">@c</strong>
class Foo {
}

vs ini:

let Foo = a(b(c(
    class Foo {
    }
})));

Saya berharap Anda dapat menggunakan yang pertama dan masih memiliki pengetikan yang tepat dari new Foo() . Perdebatannya tentang ini:

<strong i="18">@a</strong>
<strong i="19">@b</strong>
<strong i="20">@c</strong>
class Foo {
    fooMember() { 
        this.aMember(); this.bMember(); this.cMember(); 
    }
}

vs ini:

class Foo extends (<strong i="24">@a</strong> <strong i="25">@b</strong> <strong i="26">@c</strong> class { }) {
    fooMember() { 
        this.aMember(); this.bMember(); this.cMember(); 
    }
}

Yang terakhir akan bekerja tanpa merusak sistem tipe. Yang pertama bermasalah dalam beberapa cara yang telah saya ilustrasikan.

@masaeedu apakah benar-benar tidak ada kasus penggunaan terbatas di mana yang pertama dapat dibuat berfungsi?

Tepatnya: apakah tidak ada anotasi yang TypeScript dapat memberitahu kami untuk menambahkan ke a , b , dan c dari contoh Anda di atas agar sistem tipe memperlakukan yang pertama dibedakan dari yang terakhir?

Itu akan menjadi kemajuan yang mematikan untuk TypeScript.

@arackaf maaf, Anda harus membuat parameter tipe merujuk ke tipe konstruktor, bukan tipe instance:

Ini bekerja:

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

interface Blah {
  blah(): void;
}

function d<T extends Constructor>(Class: T): T & Constructor<Blah> {
  return class extends Class {
    blah() { }
  };
}

class Foo extends d(Object) {
  protected num: number;

  constructor(num: number) {
    super();
    this.num = num;
    this.blah();
  }
}

Saya hanya mencoba menggunakan dekorator.

Itu bukan masalah yang Anda coba selesaikan, dan ini adalah tautologi. Ini seperti Q: "apa yang coba dilakukan dengan palu itu?" A: "Gunakan palu saja".

Apakah ada kasus aktual yang Anda pikirkan di mana menghasilkan superclass baru dengan ekstensi yang dimaksud tidak berfungsi? Karena TypeScript mendukung itu sekarang, dan seperti yang saya katakan, jauh lebih mudah untuk bernalar tentang tipe kelas dari dalam kelas dalam kasus itu.

Saya melihat anotasi @observer Mobx dan sepertinya itu tidak mengubah bentuk kelas (dan bisa dengan mudah menjadi dekorator metode daripada dekorator kelas karena hanya membungkus render() ).

@connect sebenarnya adalah contoh yang bagus dari kerumitan mengetik dekorator dengan benar, karena @connect tampaknya bahkan tidak mengembalikan subkelas dari kelas yang didekorasi, tetapi kelas yang sama sekali baru yang membungkus kelas yang didekorasi, belum statika disalin, jadi hasilnya membuang antarmuka sisi instance dan mempertahankan sisi statis yang bahkan tidak diperiksa oleh TS dalam banyak kasus. Sepertinya @connect sebenarnya tidak boleh menjadi dekorator sama sekali dan connect seharusnya hanya digunakan sebagai HOC, karena sebagai dekorator, ini sangat membingungkan.

Saya dengan senang hati menggunakannya di JS selama lebih dari setahun

Yah ... Anda tidak benar-benar. JS tidak memiliki dekorator. Anda mungkin telah menggunakan proposal tertentu yang sudah tidak berfungsi sebelumnya, yang kebetulan diterapkan sedikit berbeda di Babel dan TypeScript. Di tempat-tempat lain saya mengusulkan dekorator untuk melanjutkan proses standarisasi, tetapi kemajuan telah melambat dan saya pikir itu belum pasti. Banyak yang berpendapat bahwa mereka tidak boleh ditambahkan, atau hanya menjadi anotasi, sehingga semantiknya mungkin masih belum jelas.

Salah satu keluhan terhadap dekorator adalah bahwa mereka dapat mengubah bentuk kelas, membuatnya sulit untuk dipikirkan secara statis, dan beberapa proposal telah dibicarakan setidaknya untuk membatasi kemampuan dekorator untuk melakukan itu, meskipun saya pikir itu sebelum proposal terbaru mendapat bahasa spesifikasi. Saya pribadi berpendapat bahwa dekorator yang berperilaku baik _tidak boleh_ mengubah bentuk kelas, sehingga untuk keperluan analisis statis mereka dapat diabaikan. Ingat bahwa JS standar tidak memiliki sistem tipe untuk bersandar untuk memberi tahu analisis apa yang dilakukan implementasi fungsi.

  • Saya hanya ingin TS mengejar ketinggalan.

Saya tidak melihat bagaimana TypeScript tertinggal di sini. Setidaknya, di balik apa? Dekorator bekerja. Apakah ada JS lain yang diketik di mana kelas berperilaku seperti yang Anda inginkan?

Dekorator sekarang tahap 2, dan digunakan di hampir setiap kerangka kerja JS.

Adapun @connect react-redux duduk di sekitar 2,5 juta unduhan per bulan; sangat arogan mendengar bahwa desainnya entah bagaimana "salah" karena Anda akan menerapkannya secara berbeda.

Upaya saat ini untuk menggunakan metode yang diperkenalkan oleh dekorator menghasilkan kesalahan ketik di TypeScript, yang memerlukan solusi seperti penggabungan antarmuka, menambahkan anotasi secara manual untuk metode yang ditambahkan, atau tidak menggunakan dekorator untuk mengubah kelas (seolah-olah siapa pun perlu diberi tahu bahwa kami hanya bisa tidak menggunakan dekorator).

Apakah benar-benar tidak ada cara untuk secara manual, dengan susah payah memberi tahu kompiler TS bahwa dekorator mengubah bentuk kelas? Mungkin ini gila tapi tidak bisakah TS mengirimkan dekorator barujenis yang bisa kita gunakan

let c : dekorator

Dan JIKA (dan hanya jika) dekorator dari tipe itu diterapkan ke suatu kelas, TS akan menganggap kelas itu bertipe Input & { blah() } ?

Secara harfiah semua orang tahu kita tidak bisa menggunakan dekorator. Sebagian besar juga tahu bahwa grup vokal tidak menyukai dekorator. Posting ini adalah tentang bagaimana TS mungkin, entah bagaimana membuat sistem tipenya memahami bahwa dekorator mengubah definisi kelas. Itu akan menjadi keuntungan besar bagi banyak orang.

Tepatnya: apakah tidak ada anotasi yang TypeScript dapat memberitahu kami untuk menambahkan ke a, b, dan c dari contoh Anda di atas agar sistem tipe memperlakukan yang pertama secara tidak dapat dibedakan dari yang terakhir?

Ya, sangat mudah untuk mendeklarasikan bentuk apa pun untuk Foo yang Anda inginkan, Anda hanya perlu menggunakan penggabungan antarmuka. Anda hanya perlu melakukan:

interface Foo extends <whateverextramembersiwantinfoo> { } 
<strong i="9">@a</strong>
<strong i="10">@b</strong>
<strong i="11">@c</strong>
class Foo { 
    /* have fun */
}

Saat ini Anda mungkin perlu mengharapkan pustaka dekorator apa pun yang Anda gunakan untuk mengekspor tipe parametrized yang sesuai dengan apa yang mereka kembalikan, atau Anda harus mendeklarasikan sendiri anggota yang mereka tambahkan. Jika kami mendapatkan #6606 Anda cukup melakukan interface Foo extends typeof(a(b(c(Foo)))) .

Upaya saat ini untuk menggunakan metode yang diperkenalkan oleh dekorator menghasilkan kesalahan ketik di TypeScript, yang memerlukan solusi seperti penggabungan antarmuka, menambahkan anotasi secara manual untuk metode yang ditambahkan, atau tidak menggunakan dekorator untuk mengubah kelas (seolah-olah siapa pun perlu diberi tahu bahwa kami hanya bisa tidak menggunakan dekorator).

Anda tidak menyebutkan opsi untuk mendekorasi kelas yang Anda tulis saat itu agnostik kepada anggota yang diperkenalkan dekorator, dan membuatnya memperluas kelas yang didekorasi saat tidak. Ini adalah bagaimana saya akan menggunakan dekorator setidaknya.

Bisakah Anda memberi saya cuplikan kode dari perpustakaan yang Anda gunakan sekarang di mana ini adalah titik nyeri?

Maaf - saya tidak mengikuti komentar terakhir Anda, tetapi komentar Anda sebelumnya, dengan penggabungan antarmuka, persis seperti cara saya mengatasinya.

Saat ini dekorator saya juga perlu mengekspor Tipe, dan saya menggunakan penggabungan antarmuka persis seperti yang Anda tunjukkan, untuk menunjukkan kepada TS bahwa anggota baru ditambahkan oleh dekorator. Kemampuan untuk membuang boilerplate tambahan inilah yang sangat diinginkan banyak orang.

@arackaf Mungkin yang Anda inginkan adalah cara penggabungan deklarasi untuk berinteraksi dengan pengetikan kontekstual argumen fungsi.

Tentu. Saya ingin kemampuan untuk menggunakan dekorator di kelas, dan secara konseptual, berdasarkan bagaimana saya mendefinisikan dekorator, agar beberapa penggabungan antarmuka terjadi, menghasilkan kelas yang didekorasi memiliki anggota tambahan yang ditambahkan.

Saya tidak tahu bagaimana saya akan membuat anotasi dekorator saya untuk mewujudkannya, tetapi saya tidak terlalu pilih-pilih - sintaks apa pun yang diberikan versi TS masa depan kepada saya untuk mempengaruhi yang akan membuat saya sangat bahagia :)

Dekorator sekarang tahap 2, dan digunakan di hampir setiap kerangka kerja JS.

Dan mereka hanya pada tahap 2 dan telah bergerak sangat lambat. Saya sangat ingin dekorator terjadi, dan saya mencoba mencari cara untuk membantu mereka bergerak, tetapi mereka masih belum pasti. Saya masih mengetahui orang-orang yang mungkin memiliki pengaruh pada proses yang menolak mereka, atau menginginkan mereka terbatas pada anotasi, meskipun saya tidak _berpikir_ mereka akan ikut campur. Maksud saya tentang implementasi dekorator kompiler saat ini bukan JS berdiri.

Adapun @connect react-redux berada di sekitar 2,5 juta unduhan per bulan; sangat arogan mendengar bahwa desainnya entah bagaimana "salah" karena Anda akan menerapkannya secara berbeda.

Harap menahan diri dari serangan pribadi seperti menyebut saya sombong.

  1. Saya tidak secara pribadi menyerang Anda, jadi jangan menyerang saya secara pribadi.
  2. Itu sama sekali tidak membantu argumen Anda.
  3. Popularitas sebuah proyek tidak boleh mengecualikannya dari kritik teknis.
  4. Apakah kita membicarakan hal yang sama? redux-connect-decorator memiliki 4 unduhan sehari. fungsi connect() react-redux terlihat seperti HOC, seperti yang saya sarankan.
  5. Saya pikir pendapat saya bahwa dekorator yang berperilaku baik tidak boleh mengubah bentuk publik kelas, dan tentu saja harus menggantinya dengan kelas yang tidak terkait, cukup masuk akal, dan akan menemukan kesepakatan yang cukup di sekitar komunitas JS. Ini jauh dari kata arogan, bahkan jika Anda tidak setuju.

Upaya saat ini untuk menggunakan metode yang diperkenalkan oleh dekorator menghasilkan kesalahan ketik di TypeScript, yang memerlukan solusi seperti penggabungan antarmuka, menambahkan anotasi secara manual untuk metode yang ditambahkan, atau tidak menggunakan dekorator untuk mengubah kelas (seolah-olah siapa pun perlu diberi tahu bahwa kami hanya bisa tidak menggunakan dekorator).

Bukannya kami hanya menyarankan untuk tidak menggunakan dekorator sama sekali, saya percaya @masaeedu mengatakan bahwa untuk tujuan memodifikasi prototipe dengan cara yang aman untuk tipe, kami memiliki mekanisme yang ada: pewarisan dan aplikasi mixin. Saya mencoba bertanya mengapa contoh d Anda tidak cocok untuk digunakan sebagai mixin, dan Anda tidak memberikan jawaban kecuali mengatakan bahwa Anda hanya ingin menggunakan dekorator.

Tidak apa-apa, saya kira, tetapi tampaknya membatasi percakapan dengan cukup kritis, jadi saya akan mundur lagi.

@justinfagnani yang saya bicarakan

import {connect} from 'react-redux'

connect hanya ada fungsi yang mengambil kelas (komponen) dan mengeluarkan yang berbeda, seperti yang Anda katakan di atas.

Saat ini, di Babel, dan TypeScript saya memiliki opsi untuk menggunakan connect seperti ini

class UnConnectedComponent extends Component {
}

let Component = connect(state => state.foo)(UnConnectedComponent);

ATAU seperti ini

@connect(state => state.foo)
class Component extends Component {
}

Saya kebetulan lebih suka yang terakhir, tetapi jika Anda atau orang lain lebih suka yang pertama, biarlah; banyak jalan menuju Roma. Saya juga lebih suka

<strong i="19">@mappable</strong>
<strong i="20">@vallidated</strong>
class Foo {
}

lebih

let Foo = validated(mappable(class Foo {
}));

atau

class Foo extends mixin(validated(mappable)) {
}
//or however that would look.

Saya tidak secara eksplisit menjawab pertanyaan Anda hanya karena saya pikir alasan saya menggunakan dekorator sudah jelas, tetapi saya akan menyatakannya secara eksplisit: Saya lebih suka yang disediakan dekorator ergonomis dan sintaksis. Keseluruhan, seluruh utas ini adalah tentang mencoba mendapatkan beberapa kemampuan untuk memberi tahu TS bagaimana dekorator kami mengubah kelas yang dimaksud sehingga kami tidak memerlukan boilerplate seperti penggabungan antarmuka.

Seperti yang dikatakan OP saat itu, kami hanya ingin

declare function Blah<T>(target: T): T & {foo: number}

<strong i="31">@Blah</strong>
class Foo {
    bar() {
        return this.foo; // Property 'foo' does not exist on type 'Foo'
    }
}

new Foo().foo; // Property 'foo' does not exist on type 'Foo'

untuk bekerja saja. Fakta bahwa ada alternatif lain untuk sintaks khusus ini jelas dan tidak material. Fakta bahwa banyak komunitas JS/TS tidak menyukai sintaks khusus ini juga jelas dan tidak penting.

Jika TS dapat memberi kami cara apa pun, betapapun terbatasnya, untuk membuat sintaks ini berfungsi, itu akan menjadi manfaat besar bagi bahasa dan komunitas.

@arackaf Anda lupa menyebutkan bagaimana menggunakan connect mengharuskan mengakses anggota tambahan pada this . AFAICT, implementasi komponen Anda seharusnya benar-benar agnostik terhadap apa yang dilakukan connect , ia hanya menggunakan props dan state miliknya sendiri. Jadi dalam hal itu cara connect dirancang lebih sesuai dengan penggunaan dekorator @justinfagnani daripada milik Anda.

Tidak juga. Mempertimbangkan

@connect(state => state.stuffThatSatisfiesPropsShape)
class Foo extends Component<PropsShape, any> {
}

lalu nanti

<Foo />

yang seharusnya valid - PropsShape props berasal dari toko Redux, tetapi TypeScript tidak mengetahui hal ini, karena itu keluar dari definisi asli Foo, jadi saya akhirnya mendapatkan kesalahan untuk props yang hilang, dan perlu memasukkannya sebagai any .

Tetapi untuk lebih jelasnya, kasus penggunaan yang tepat yang awalnya diberikan oleh OP, dan diduplikasi dalam komentar kedua saya di atas di sini, sangat umum di basis kode kami, dan dari kelihatannya, banyak lainnya juga. Kami benar-benar menggunakan hal-hal seperti

<strong i="6">@validated</strong>
<strong i="7">@mappable</strong>
class Foo {
}

dan harus menambahkan beberapa penggabungan antarmuka untuk memenuhi TypeScript. Akan luar biasa jika memiliki cara untuk memanggang antarmuka yang digabungkan ke dalam definisi dekorator secara transparan.

Kami terus berputar-putar dengan ini. Kami setuju bahwa tipe Foo harus menjadi tipe pengembalian connect . Kami sangat setuju dalam hal ini.

Di mana kami berbeda adalah apakah anggota di dalam Foo perlu berpura-pura bahwa this adalah semacam penggabungan rekursif dari tipe pengembalian dekorator dan "Foo asli" apa pun artinya. Anda belum menunjukkan kasus penggunaan untuk ini.

Di mana kami berbeda adalah apakah anggota di dalam Foo perlu berpura-pura bahwa ini adalah semacam penggabungan rekursif dari tipe pengembalian dekorator dan "Foo asli" apa pun artinya. Anda belum menunjukkan kasus penggunaan untuk ini.

Saya mempunyai. Lihat komentar saya di atas, dan dalam hal ini kode asli OP. Ini adalah kasus penggunaan yang sangat umum.

Tolong tunjukkan saya apa yang connect tambahkan ke prototipe yang diharapkan Anda akses di dalam komponen. Jika jawabannya "tidak ada", maka tolong jangan memunculkan connect lagi, karena sama sekali tidak relevan.

@masaeedu cukup adil, dan saya setuju. Saya sebagian besar menanggapi pernyataan @ justinfagnani tentang bagaimana dekorator tidak boleh memodifikasi kelas asli, connect dirancang dengan buruk, dll, dll.

Saya hanya mendemonstrasikan sintaks yang saya dan banyak orang lain sukai, terlepas dari kenyataan bahwa ada opsi lain.

Untuk kasus ini, ya, saya kebetulan tidak tahu perpustakaan npm populer yang mengambil kelas, dan memuntahkan kembali kelas baru dengan metode baru, yang akan digunakan di dalam kelas yang didekorasi. Ini adalah sesuatu yang sering saya dan orang lain lakukan, dalam kode kami sendiri, untuk mengimplementasikan mixin kami sendiri, tetapi saya tidak benar-benar memiliki contoh di npm.

Tapi itu tidak benar-benar diperdebatkan bahwa ini adalah kasus penggunaan yang umum, bukan?

@arackaf Tidak, bukan karena Anda tidak mengakses anggota di this yang diperkenalkan oleh @connect . Sebenarnya saya cukup yakin cuplikan yang tepat ini:

@connect(state => state.stuffThatSatisfiesPropsShape)
class Foo extends Component<PropsShape, any> {
    render(){
        this.props.stuffFromPropsShape // <----- added by decorator
    }
}

akan dikompilasi tanpa masalah di TypeScript hari ini. Cuplikan yang Anda tempel tidak ada hubungannya dengan fitur yang Anda minta.

@masaeedu - maaf - saya sadar saya salah dan menghapus komentar itu. Tidak cukup cepat untuk mencegah Anda membuang-buang waktu :(

@arackaf Saya tidak tahu seberapa umum pola itu, dan saya pribadi tidak menggunakannya sendiri atau menggunakan perpustakaan apa pun yang menggunakannya. Saya menggunakan Angular 2 untuk sementara waktu, jadi saya tidak pernah menggunakan dekorator dalam hidup saya. Inilah mengapa saya meminta contoh spesifik penggunaan perpustakaan di mana ketidakmampuan untuk mengakses anggota yang diperkenalkan dekorator di penghias adalah titik nyeri.

Dalam setiap kasus di mana anggota kelas mengharapkan sesuatu dari this , harus ada sesuatu di badan kelas atau di klausa extends kelas yang memenuhi persyaratan. Ini selalu berhasil, bahkan dengan dekorator . Jika Anda memiliki dekorator yang menambahkan beberapa fungsi yang bergantung pada kelas, kelas tersebut harus memperluas apa pun yang dikembalikan dekorator, dan bukan sebaliknya. Itu hanya akal sehat.

Saya tidak yakin mengapa ini masuk akal. Ini adalah kode dari basis kode kami saat ini. Kami memiliki dekorator yang menambahkan metode ke prototipe kelas. Kesalahan TypeScript keluar. Pada saat itu saya hanya melemparkan deklarasi ini di sana. Saya bisa menggunakan penggabungan antarmuka.

Tapi alangkah baiknya jika tidak digunakan sama sekali. Akan sangat, sangat menyenangkan untuk dapat memberi tahu TypeScript bahwa "dekorator ini di sini menambahkan metode ini ke kelas apa pun yang diterapkannya - izinkan this di dalam kelas untuk mengaksesnya juga"

Banyak orang lain di utas ini tampaknya menggunakan dekorator dengan cara yang sama, jadi saya harap Anda akan mempertimbangkan bahwa mungkin tidak masuk akal bahwa ini tidak akan berhasil.

image

@arackaf Ya, jadi Anda harus melakukan export class SearchVm extends (@mappable({...}) class extends SearchVmBase {}) . Itu adalah SearchVm yang bergantung pada kemampuan pemetaan, bukan sebaliknya.

kelas ekspor perluasan SearchVm (@mappable({...}) perluasan kelas SearchVmBase {})

Inti dari utas ini adalah untuk MENGHINDARI boilerplate seperti itu. Dekorator memberikan DX yang jauh lebih bagus daripada harus melakukan hal-hal seperti itu. Ada alasan mengapa kami memilih untuk menulis kode seperti yang saya miliki, daripada itu.

Jika Anda membaca komentar di utas ini, saya harap Anda akan diyakinkan bahwa banyak orang lain mencoba menggunakan sintaks dekorator yang lebih sederhana, dan karena itu mempertimbangkan kembali apa yang "harus" kita lakukan, atau apa yang "akal sehat", dll.

Apa yang Anda inginkan tidak ada hubungannya dengan dekorator secara khusus. Masalah Anda adalah bahwa mekanisme TypeScript untuk mengatakan "hei TypeScript, hal-hal yang terjadi pada struktur ini yang tidak Anda ketahui, izinkan saya memberi tahu Anda semuanya", saat ini terlalu terbatas. Saat ini kami hanya memiliki penggabungan antarmuka, tetapi Anda ingin fungsi dapat menerapkan penggabungan antarmuka ke argumennya, yang tidak secara khusus terkait dengan dekorator dengan cara apa pun.

Saat ini kami hanya memiliki penggabungan antarmuka, tetapi Anda ingin fungsi dapat menerapkan penggabungan antarmuka ke argumennya, yang tidak secara khusus terkait dengan dekorator dengan cara apa pun.

Baik. Cukup adil. Apakah Anda ingin saya membuka edisi terpisah, atau Anda ingin mengganti nama yang satu ini, dll?

@arackaf Satu-satunya "boilerplate" adalah kenyataan bahwa saya memiliki class { } , yang merupakan a) tetap 7 karakter, tidak peduli berapa banyak dekorator yang Anda gunakan b) akibat fakta bahwa dekorator tidak dapat (belum ) diterapkan pada ekspresi pengembalian kelas yang sewenang-wenang, dan c) karena saya secara khusus ingin menggunakan dekorator untuk keuntungan Anda. Formulasi alternatif ini:

export class SearchVm extends mappable({...})(SearchVmBase)
{
}

tidak lebih verbose dari apa yang Anda lakukan awalnya.

Baik. Cukup adil. Apakah Anda ingin saya membuka edisi terpisah, atau Anda ingin mengganti nama yang satu ini, dll?

Masalah terpisah akan lebih baik.

tidak lebih bertele-tele dari apa yang Anda lakukan awalnya

Ini tidak lagi bertele-tele, tetapi saya sangat berharap Anda akan mempertimbangkan fakta bahwa banyak yang tidak menyukai sintaksis itu. Baik atau buruk, banyak yang lebih menyukai penawaran dekorator DX.

Masalah terpisah akan lebih baik.

Saya akan mengetik satu besok, dan menautkan ke yang ini untuk konteks.

Terima kasih telah membantu menyelesaikan ini :)

Jika saya boleh masuk, @masaeedu saya tidak setuju dengan pernyataan ini:

Apa yang Anda inginkan tidak ada hubungannya dengan dekorator secara khusus. Masalah Anda adalah bahwa mekanisme TypeScript untuk mengatakan "hei TypeScript, hal-hal yang terjadi pada struktur ini yang tidak Anda ketahui, izinkan saya memberi tahu Anda semuanya", saat ini terlalu terbatas. Saat ini kami hanya memiliki penggabungan antarmuka, tetapi Anda ingin fungsi dapat menerapkan penggabungan antarmuka ke argumennya, yang tidak secara khusus terkait dengan dekorator dengan cara apa pun.

Tentunya TypeScript dapat melihat tipe kembalinya dekorator kelas untuk mengetahui tipe kelas yang bermutasi seharusnya. Tidak perlu "menerapkan penggabungan antarmuka ke argumen". Jadi seperti yang saya lihat, ini sepenuhnya berkaitan dengan dekorator.

Juga, kasus TypeScript yang tidak mengetahui bahwa komponen yang terhubung tidak memerlukan alat peraga lagi adalah salah satu aspek yang membuat saya tidak bisa menggunakan Redux dengan React dan TypeScript.

@codeandcats Saya telah berputar-putar dengan ini sejak utas dimulai, dan saya benar-benar tidak dapat melakukannya lagi. Silakan lihat diskusi sebelumnya dengan seksama, dan coba pahami apa yang saya tidak setuju dengan @arackaf .

Saya setuju bahwa ketika Anda melakukannya <strong i="8">@bar</strong> class Foo { } , tipe Foo harus menjadi tipe pengembalian bar . Saya tidak setuju bahwa this di dalam Foo harus terpengaruh dengan cara apa pun. Menggunakan connect sama sekali tidak relevan dengan titik pertikaian di sini, karena connect tidak mengharapkan komponen yang dihias untuk mengetahui dan menggunakan anggota yang ditambahkan ke prototipe.

Saya dapat memahami Anda frustrasi @masaeedu tetapi jangan berasumsi hanya karena saya belum secara aktif menyuarakan pendapat saya dalam bolak-balik yang Anda alami selama 48 jam terakhir bahwa saya belum mengikuti diskusi - jadi tolong lepaskan aku dari sikap merendahkan itu.

Seperti yang saya pahami, Anda berpikir di dalam kelas yang didekorasi, ia seharusnya tidak tahu tentang mutasi apa pun yang dibuat oleh dekorator, tetapi di luar seharusnya. Saya tidak setuju dengan itu. Fakta bahwa @arackaf berpikir di dalam kelas harus melihat versi yang bermutasi adalah selain inti dari komentar saya.

Either way, TypeScript bisa menggunakan tipe yang dikembalikan oleh fungsi dekorator sebagai sumber kebenaran untuk kelas. Dan karenanya ini adalah masalah Dekorator, bukan fungsionalitas mutasi argumen aneh baru seperti yang Anda sarankan, yang sejujurnya hanya terdengar seperti upaya manusia jerami.

@Zalastax yang menggunakan fungsi dekorator sebagai fungsi, bukan sebagai dekorator. Jika Anda memiliki beberapa dekorator yang perlu Anda terapkan, Anda perlu membuat sarangnya, yang secara teknis tidak lagi bertele-tele tetapi tidak menyenangkan secara sintaksis - tahukah Anda apa yang saya maksud?

merendahkan dll.

Kebisingan.

Fakta bahwa @arackaf berpikir di dalam kelas harus melihat versi yang bermutasi adalah selain inti dari komentar saya.

Ini adalah poin sentral dari paragraf yang Anda tanggapi, jadi jika Anda berbicara tentang sesuatu yang lain, itu tidak relevan. Apa yang diinginkan @arackaf adalah, secara kasar, penggabungan antarmuka pada argumen fungsi. Ini lebih baik ditangani oleh fitur independen dari dekorator. Yang Anda inginkan (yang tampaknya identik dengan yang saya inginkan) adalah memperbaiki bug di TypeScript di mana <strong i="12">@foo</strong> class Foo { } tidak menghasilkan tanda tangan tipe yang sama untuk Foo sebagai const Foo = foo(class { }) . Hanya yang terakhir yang secara khusus terkait dengan dekorator.

Seperti yang saya pahami, Anda berpikir di dalam kelas yang didekorasi, ia seharusnya tidak tahu tentang mutasi apa pun yang dibuat oleh dekorator, tetapi di luar seharusnya. Saya tidak setuju dengan itu

Jika Anda memutuskan untuk membicarakan sesuatu yang kita sepakati, tetapi mengawalinya dengan "Saya tidak setuju dengan pernyataan ini", maka Anda hanya membuang-buang waktu.

Bisakah saya mendapatkan kentang goreng dengan garam ini?

Oh saya mengerti, ketika Anda menggurui rekan-rekan Anda baik-baik saja tetapi jika orang-orang menarik Anda ke atasnya, itu kebisingan.

Saya tidak mengerti mengapa konsep "mutasi argumen" Anda diperlukan untuk mengimplementasikan apa yang diusulkan @arackaf .

Terlepas dari apakah kami setuju dengan @arackaf atau tidak, TypeScript dapat dengan mudah menyimpulkan tipe sebenarnya dari hasil dekorator, menurut pendapat saya.

Mohon maaf jika Anda merasa direndahkan; niatnya adalah untuk membuatmu
sebenarnya utas bertele-tele raksasa yang bisa dimaafkan siapa pun
peluncuran. Saya mendukung ini, karena masih cukup jelas Anda tidak sadar
detail dan "tidak setuju" dengan saya berdasarkan kesalahpahaman tentang
posisi saya.

Misalnya, pertanyaan Anda tentang mengapa antarmuka bergabung pada fungsi
argumen memecahkan masalah Adam paling baik dijawab dengan melihat yang relevan
diskusi dengan Adam, di mana dia mengatakan dia sudah menggunakan penggabungan antarmuka,
tetapi merasa kesal karena harus melakukan ini di setiap situs deklarasi.

Saya pikir semua aspek teknis ini telah dipukuli sampai mati. Saya
ga mau threadnya didominasi percekcokan antar pribadi, jadi begini
akan menjadi posting terakhir saya.

Memang benar, ada dua bug dengan dekorator: tipe pengembalian tidak dihormati, dan internal ke kelas, anggota yang ditambahkan tidak dihormati ( this di dalam kelas). Dan ya, saya lebih peduli dengan yang terakhir, karena yang pertama lebih mudah dikerjakan.

Saya telah membuka kasing terpisah di sini: https://github.com/Microsoft/TypeScript/issues/16599

Hai semuanya, saya melewatkan percakapan ini selama akhir pekan, dan mungkin tidak ada banyak alasan untuk membawanya kembali sekarang; tapi saya ingin mengingatkan semua orang bahwa di tengah panasnya diskusi semacam ini, setiap orang yang menimbang biasanya bermaksud baik. Menjaga nada hormat dapat membantu memperjelas dan mengarahkan percakapan, dan dapat mendorong kontributor baru. Melakukan ini tidak selalu mudah (terutama ketika internet menyerahkan nada suara kepada pembaca), tetapi saya pikir ini penting untuk skenario masa depan. 😃.

Ini statusnya apa?

@alex94puchades ini masih proposal tahap 2 , jadi mungkin masih ada waktu. TC39 tampaknya memiliki beberapa gerakan di atasnya , setidaknya.

Menurut komentar ini , sepertinya bisa diusulkan untuk tahap 3 pada awal November.

cara solusi untuk mengubah tanda tangan suatu fungsi oleh dekorator

tambahkan fungsi wapper kosong

export default function wapper (cb: any) {
    return cb;
}

tambahkan definisi

export function wapper(cb: IterableIterator<0>): Promise<any>;

hasil

<strong i="13">@some</strong> decorator // run generator and return promise
function *abc() {}

wapper(abc()).then() // valid

/ping

Jika ada yang mencari solusi untuk ini, satu solusi yang saya buat ada di bawah.

Ini bukan solusi terbaik karena memerlukan objek bersarang, tetapi bagi saya ini berfungsi dengan baik karena saya sebenarnya ingin properti dekorator berada di objek dan tidak hanya datar pada instance kelas.

Ini adalah sesuatu yang saya gunakan untuk modals Angular 5 saya.

Berpura-pura saya memiliki @ModalParams(...) dekorator yang dapat saya gunakan pada custom ConfirmModalComponent . Agar properti dari dekorator @ModalParams(...) muncul dalam komponen kustom saya, saya perlu memperluas kelas dasar yang memiliki properti yang akan ditetapkan nilainya oleh dekorator.

Misalnya:

export class Modal {
    params: any;

    constructor(values: Object = {}) {
        Object.assign(this, values);
    }
}

export function ModalParams (params?: any) {
    return (target: any): void  => {
        Object.assign(target.prototype, {
            params: params
        });
    };
}

@Component({...})
@ModalOptions({...})
@ModalParams({
    width:             <number> 300,
    title:             <string> 'Confirm',
    message:           <string> 'Are you sure?',
    confirmButtonText: <string> 'Yes',
    cancelButtonText:  <string> 'No',
    onConfirm:         <(modal: ConfirmModalComponent) => void> (() => {}),
    onCancel:          <(modal: ConfirmModalComponent) => void> (() => {})
})
export class ConfirmModalComponent extends Modal {
    constructor() {
        super();
    }

    confirm() {
        this.params.onConfirm(this); // This does not show a syntax error 
    }

    cancel() {
        this.params.onCancel(this); // This does not show a syntax error 
    }
}

Sekali lagi, ini tidak terlalu cantik, tetapi berfungsi dengan baik untuk kasus penggunaan saya, jadi saya pikir orang lain mungkin menganggapnya berguna.

@lansana tetapi Anda tidak mendapatkan tipenya bukan?

@confraria Sayangnya tidak, tetapi mungkin ada cara untuk mencapainya jika Anda menerapkan kelas Modal generik yang Anda perluas. Misalnya sesuatu seperti ini mungkin berfungsi (tidak diuji):

export class Modal<T> {
    params: T;
}

export function ModalParams (params?: any) {
    return (target: any): void  => {
        Object.assign(target.prototype, {
            params: params
        });
    };
}

// The object in @ModalParams() should be of type MyType
@ModalParams({...})
export class ConfirmModalComponent extends Modal<MyType> {
    constructor() {
        super();
    }
}

:/ yup tetapi kemudian jenisnya dipisahkan dari dekorator dan Anda tentu tidak dapat menggunakan keduanya .. :( selain itu Anda akan mendapatkan kesalahan jika kelas tidak mengimplementasikan metode .. :( saya rasa tidak ada adalah cara untuk mendapatkan tipe yang tepat dengan pola ini saat ini

Ya, alangkah baiknya jika ini memungkinkan, membuat TypeScript jauh lebih baik dan ekspresif. Semoga sesuatu akan segera datang.

@lansana Ya, intinya adalah alangkah baiknya jika dekorator kelas dapat mengubah tanda tangan kelas sendiri, tanpa mengharuskan kelas untuk memperluas atau mengimplementasikan hal lain (karena itu adalah duplikasi upaya dan jenis informasi) .

Catatan tambahan: dalam contoh Anda di sana, perhatikan bahwa params akan statis di semua instance kelas komponen modal yang didekorasi, karena ini adalah referensi objek. Meskipun mungkin itu dengan desain. : - ) Tapi saya ngelantur...

Sunting: Saat saya memikirkan hal ini, saya dapat melihat kontra dari mengaktifkan dekorator untuk memodifikasi tanda tangan kelas. Jika implementasi kelas dengan jelas menampilkan anotasi jenis tertentu, tetapi seorang dekorator mampu masuk dan mengubah semua itu, itu akan menjadi sedikit trik yang kejam untuk dimainkan pada pengembang. Banyak kasus penggunaan ternyata menyangkut penggabungan logika kelas baru, jadi sayangnya banyak dari mereka akan lebih difasilitasi melalui ekstensi atau implementasi antarmuka - yang juga berkoordinasi dengan tanda tangan kelas yang ada dan menimbulkan kesalahan yang sesuai di mana tabrakan terjadi. Kerangka kerja seperti Angular tentu saja banyak menggunakan dekorator untuk menambah kelas, tetapi desainnya tidak memungkinkan kelas untuk "mendapatkan" atau mencampur logika baru dari dekorator yang kemudian dapat digunakan dalam implementasinya sendiri - ini untuk mengisolasi logika kelas dari logika koordinasi kerangka kerja. Itu pendapat _humble_ saya. : - )

Tampaknya lebih baik menggunakan kelas tingkat tinggi daripada dekorator + peretasan. Saya tahu orang ingin menggunakan dekorator untuk hal ini tetapi menggunakan compose + HOC adalah cara yang harus dilakukan dan mungkin akan tetap seperti ini ... selamanya ;) Menurut MS dll. dekorator hanya untuk melampirkan metadata ke kelas dan ketika Anda periksa pengguna besar dekorator seperti Angular Anda akan melihat mereka hanya digunakan dalam kapasitas ini. Saya ragu Anda akan dapat meyakinkan pengelola TypeScript sebaliknya.

Sangat menyedihkan dan agak aneh bahwa fitur yang begitu kuat, yang memungkinkan komposisi fitur yang sebenarnya, dan yang menghasilkan keterlibatan seperti itu telah diabaikan oleh tim TS begitu lama sekarang.

Ini benar-benar fitur yang dapat merevolusi cara kita menulis kode; memungkinkan semua orang untuk merilis mixin kecil pada hal-hal seperti Bitly atau NPM dan memiliki penggunaan kembali kode yang sangat mengagumkan di TypeScript. Dalam proyek saya sendiri, saya akan langsung membuat @Poolable @Initable @Translating saya dan mungkin lebih banyak lagi.

Tolong semua tim inti TS yang perkasa. "Yang Anda butuhkan" untuk diterapkan adalah antarmuka yang dikembalikan harus dihormati.

// taken from my own lib out of context
export function Initable<T extends { new(...args: any[]): {} }>(constructor: T): T & Constructor<IInitable<T>> {
    return class extends constructor implements IInitable<T> {
        public init(obj: Partial<T> | any, mapping?: any) {
            setProperties(this, obj, mapping);
            return this
        }
    }
}

yang memungkinkan kode ini berjalan tanpa keluhan:

<strong i="14">@Initable</strong>
class Person {
    public name: string = "";
    public age: number = 0;
    public superPower: string | null = null;
}
let sam = new Person();

sam.init({age: 17, name: "Sam", superPower: "badassery"});

@AllNamesRTaken

Meskipun saya setuju dengan poin Anda, Anda dapat mencapai hal yang sama seperti:

class Animal {
    constructor(values: Object = {}) {
        Object.assign(this, values);
    }
}

Dan kemudian Anda bahkan tidak memerlukan fungsi init, Anda bisa melakukan ini:

const animal = new Animal({name: 'Fred', age: 1});

@lansana
Ya tapi itu akan mencemari konstruktor saya yang tidak selalu super duper apa yang saya inginkan.

Saya juga memiliki mixin serupa untuk membuat objek apa pun Dapat Dikumpulkan dan hal-hal lain. Hanya kemungkinan untuk menambahkan fungsionalitas melalui komposisi yang dibutuhkan. Contoh init hanyalah cara kebutuhan untuk melakukan apa yang Anda lakukan tanpa mencemari konstruktor, membuatnya berguna dengan kerangka kerja lain.

@AllNamesRTaken Tidak ada gunanya menyentuh dekorator sekarang karena sudah dalam keadaan ini begitu lama dan proposal sudah berada di tahap 2. Tunggu https://github.com/tc39/proposal-decorators untuk diselesaikan dan kemungkinan besar kami akan mendapatkannya dalam bentuk apa pun yang disetujui semua orang.

@Kukkimonsuta
Saya sangat tidak setuju dengan Anda karena apa yang saya minta adalah murni tentang jenis dan bukan fungsionalitas. Oleh karena itu tidak ada hubungannya dengan hal-hal ES. Solusi saya di atas sudah berfungsi, saya hanya tidak ingin harus melakukan casting ke.

@AllNamesRTaken Anda dapat melakukan ini _today_ dengan mixin tanpa peringatan sama sekali:

function setProperties(t: any, o: any, mapping: any) {}

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

interface IInitable<T> {
  init(obj: Partial<T> | any, mapping?: any): this;
}

// taken from my own lib out of context
function Initable<T extends Constructor<{}>>(constructor: T): T & Constructor<IInitable<T>> {
    return class extends constructor implements IInitable<T> {
        public init(obj: Partial<T> | any, mapping?: any) {
            setProperties(this, obj, mapping);
            return this
        }
    }
}

class Person extends Initable(Object) {
    public name: string = "";
    public age: number = 0;
    public superPower: string | null = null;
}
let sam = new Person();
sam.init({age: 17, name: "Sam", superPower: "badassery"});

Di masa depan, jika kita memasukkan proposal mixin ke dalam JS, kita dapat melakukan ini:

mixin Initable {
  public init(obj: Partial<T> | any, mapping?: any) {
    setProperties(this, obj, mapping);
    return this
  }
}

class Person extends Object with Initable {
    public name: string = "";
    public age: number = 0;
    public superPower: string | null = null;
}
let sam = new Person();
sam.init({age: 17, name: "Sam", superPower: "badassery"});

@justinfagnani mixin adalah bagaimana saya memulai dengan fitur-fitur ini dan implementasi saya sebenarnya juga berfungsi seperti yang Anda jelaskan:

class Person extends Initable(Object) {
    public name: string = "";
    public age: number = 0;
    public superPower: string | null = null;
}
let sam = new Person();     
sam.init({age: 17, name: "Sam", superPower: "badassery"});

yang bagus, sebagai solusi, tetapi tidak benar-benar menghilangkan manfaat dari mengizinkan dekorator mengubah jenis menurut saya.

EDIT: juga kehilangan pengetikan pada parsial untuk init.

Dengan fitur ini Anda akan dapat menulis HoC dengan lebih mudah
Saya harap fitur ini akan ditambahkan

@kgtkr itulah alasan utama mengapa saya sangat menginginkan ini...

Ada juga sedikit keadaan darurat dalam definisi react-router karena beberapa orang memutuskan bahwa keamanan tipe lebih penting daripada memiliki antarmuka dekorasi kelas. Alasan dasarnya adalah bahwa withRouter membuat beberapa props opsional.

Sekarang tampaknya ada perseteruan, di mana orang dipaksa untuk menggunakan antarmuka fungsi withRouter alih-alih decoration .

Mulai memecahkan fitur ini akan membuat dunia menjadi tempat yang lebih bahagia.

Perseteruan yang sama* terjadi beberapa waktu lalu dengan pengetikan material-ui dan withStyle , yang dirancang untuk digunakan sebagai dekorator, tetapi, untuk digunakan dengan cara yang aman, pengguna TypeScript harus menggunakan sebagai fungsi reguler. Meskipun sebagian besar debu telah menetap di sana, itu adalah sumber kebingungan yang berkelanjutan bagi pendatang baru di proyek ini!

* Yah, "permusuhan" mungkin kata yang kuat

Saya telah menonton ini untuk waktu yang lama, dan sampai tiba, semoga orang lain dapat mengambil manfaat dari peretasan kecil saya untuk mencapai perilaku ini dengan cara yang kompatibel ke depan. Jadi ini dia, menerapkan metode kelas kasus gaya Scala super dasar copy ...

Saya telah menerapkan dekorator seperti ini:

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

interface CaseClass {
  copy(overrides?: Partial<this>): this
}

function CaseClass<T extends Constructor<{}>>(constructor: T): T & Constructor<CaseClass> {
  return class extends constructor implements CaseClass {
    public copy(overrides: Partial<this> = {}): this {
      return Object.assign(Object.create(Object.getPrototypeOf(this)), this, overrides);
    }
  }
}

Kode ini membuat kelas anonim dengan metode copy terlampir. Ini bekerja persis seperti yang diharapkan dalam JavaScript saat berada di lingkungan yang mendukung dekorator.

Untuk menggunakan ini di TypeScript, dan benar-benar memiliki sistem tipe yang mencerminkan metode baru di kelas yang ditargetkan, peretasan berikut dapat digunakan:

class MyCaseClass extends CaseClass(class {
  constructor(
    public fooKey: string,
    public barKey: string,
    public bazKey: string
  ) {}
}) {}

Semua instance MyCaseClass akan mengekspos pengetikan untuk metode copy yang diwarisi dari kelas anonim di dalam dekorator CaseClass . Dan, ketika TypeScript mendukung mutasi dari tipe yang dideklarasikan, kode ini dapat dimodifikasi dengan mudah ke sintaks dekorator biasa <strong i="18">@CaseClass</strong> etc tanpa kesalahan yang tidak terduga.

Itu akan sangat bagus untuk melihat ini di rilis TypeScript utama berikutnya - Saya percaya bahwa ini akan membantu kode yang lebih bersih dan lebih ringkas daripada mengekspor kekacauan kelas proxy yang aneh.

Kapan fitur ini akan tersedia?

Saya ingin menggunakan dekorator kelas untuk menangani tugas yang berulang.
Tapi sekarang ketika menginisialisasi kelas, saya mendapatkan kesalahan berikut: Expected 0 arguments, but got 1

function Component<T extends { new(...args: any[]): {} }>(target: T) {
    return class extends target {
        public constructor(...args: any[]) {
            super(...args);
            resolveDependencies(this, args[0])
        }
    }
}

<strong i="8">@Component</strong>
export class ExampleService {
    @Inject(ExampleDao) private exampleDao: ExampleDao;

    // <strong i="9">@Component</strong> will automatically do this for me 
    // public constructor(deps: any) {
    //  resolveDependencies(this, deps);
    // }

    public getExample(id: number): Promise<Example | undefined> {
        return this.exampleDao.getOne(id);
    }
}

new ExampleService({ exampleDao }) // TS2554: Expected 0 arguments, but got 1.

Saya harap fitur ini akan segera tersedia! :)

@iainreid820 apakah Anda mengalami bug aneh menggunakan pendekatan ini?

Penantian yang lama! Sementara itu, apakah ini diselesaikan dengan apa pun di peta jalan saat ini?
Seperti masalah 5453 ?

Saya menggunakan Material UI dan baru menyadari bahwa TypeScript tidak mendukung sintaks dekorator untuk withStyles : https://material-ui.com/guides/typescript/#decorating -components

Harap perbaiki batasan itu di rilis TypeScript berikutnya. Dekorator kelas sepertinya tidak berguna bagiku sekarang.

Sebagai pengelola Morphism Js , ini adalah batasan besar untuk jenis perpustakaan ini. Saya ingin menghindari konsumen Penghias Fungsi harus menentukan target Jenis Fungsi https://github.com/nobrainr/morphism# --toclassobject-decorator , jika tidak menggunakan dekorator alih-alih HOF terdengar agak tidak berguna 😕.
Apakah ada rencana untuk mengatasi ini? Apakah ada cara untuk membantu mewujudkan fitur ini? Terima kasih sebelumnya!

@bikeshedder bahwa contoh Material UI adalah kasus penggunaan mixin kelas, dan Anda bisa mendapatkan tipe yang tepat dari mixin. Dari pada:

const DecoratedClass = withStyles(styles)(
  class extends React.Component<Props> {
...
}

menulis:

class DecoratedClass extends withStyles(styles)(React.Component<Props>) {
...
}

@justinfagnani Itu tidak berhasil untuk saya:

ss 2018-10-16 at 10 00 47

Ini kode saya: https://Gist.github.com/G-Rath/654dff328dbc3ae90d16caa27a4d7262

@G-Rath saya pikir new () => React.Component<Props, State> bukannya harus bekerja ?

@emyann Tidak ada dadu. Saya telah memperbarui intinya dengan kode baru, tetapi apakah ini yang Anda maksud?

class CardSection extends withStyles(styles)(new () => React.Component<Props, State>) {

Tidak peduli bagaimana Anda memformatnya, extends withStyles(styles)(...) pada dasarnya tidak terdengar seperti saran yang tepat. withStyles BUKAN campuran kelas.

withStyles mengambil kelas komponen A dan membuat kelas B yang ketika dirender merender kelas A dan meneruskan props ke kelas itu + prop classes .

Jika Anda memperluas nilai pengembalian withStyles maka Anda memperluas pembungkus B , alih-alih mengimplementasikan kelas A yang benar-benar menerima prop classes .

Tidak peduli bagaimana Anda memformatnya, extends withStyles(styles)(...) pada dasarnya tidak terdengar seperti saran yang tepat. withStyles BUKAN campuran kelas.

Memang. Tidak yakin mengapa ada begitu banyak penolakan di sini.

Yang mengatakan, dekorator sangat dekat dengan tahap 3, dari apa yang saya dengar, jadi saya membayangkan TS akan mendapatkan dukungan yang tepat di sini segera.

Saya mendengar orang-orang yang menginginkan sintaks yang lebih bersih, khususnya. saat menggunakan penerapan beberapa HoC sebagai dekorator. Solusi sementara yang kami gunakan adalah benar-benar membungkus beberapa dekorator di dalam fungsi pipa dan kemudian menggunakan fungsi murni itu untuk menghias komponen. misalnya

@flow([
  withStyles(styles),
  connect(mapStateToProps),
  decorateEverything(),
])
export class HelloWorld extends Component<Props, State> {
  ...
}

di mana flow adalah lodash.flow . Banyak perpustakaan util menyediakan sesuatu seperti ini - recompose , Rx.pipe dll. tetapi Anda tentu saja dapat menulis fungsi pipa sederhana Anda sendiri jika Anda tidak ingin menginstal perpustakaan.

Saya merasa ini lebih mudah dibaca daripada tidak menggunakan dekorator,

export const decoratedHelloWorld = withStyles(styles)(
  connect(mapStateToProps)(
    decorateEverything(
       HelloWorld
))))

Alasan lain saya menggunakan ini, adalah bahwa pola ini mudah ditemukan-dan-diganti-dapat/dapat-grep setelah spesifikasi dekorator diumumkan dan didukung dengan benar.

@justinfagnani Anehnya saya mendapatkan kesalahan sintaks dari ESLint ketika saya mencoba mengubah extends React.Component<Props, State> menjadi extends withStyles(styles)(React.Component<Props, State>) , jadi saya tidak bisa memverifikasi kesalahan ketik @G-Rath dengan cara yang sama.

Tetapi saya memperhatikan bahwa jika saya melakukan sesuatu seperti berikut maka masalah dengan new (mungkin masalah yang sama?) muncul:

class MyComponent extends React.Component<Props, State> {
  /* ... */
}

const _MyComponent = withStyles(styles)(MyComponent)
const test = new _MyComponent // <--------- ERROR

dan kesalahannya adalah:

Cannot use 'new' with an expression whose type lacks a call or construct signature.

Apakah ini berarti tipe yang dikembalikan oleh Material UI bukan konstruktor (meskipun seharusnya demikian)?

@sagar-sm Tapi, apakah Anda mendapatkan tipe yang benar ditambah dengan tipe Material UI? Sepertinya Anda tidak akan melakukannya.

Untuk referensi, saya menemukan ini karena saya juga mencoba menggunakan withStyles Material UI sebagai dekorator, yang tidak berfungsi, jadi saya mengajukan pertanyaan ini: https://stackoverflow.com/questions/53138167

membutuhkan ini, maka kita dapat membuat fungsi async kembali sebagai bluebird

Saya mencoba melakukan sesuatu yang serupa. ATM Saya menggunakan solusi berikut:

Pertama, inilah dekorator "mixin" saya

export type Ctor<T = {}> = new(...args: any[]) => T 
function mixinDecoratorFactory<MixinInterface>() {
    return function(toBeMixed: MixinInterface) {
        return function<MixinBase extends Ctor>(MixinBase: MixinBase) {
            Object.assign(MixinBase.prototype, toBeMixed)
            return class extends MixinBase {} as MixinBase & Ctor<MixinInterface>
        }
    }
}

Buat dekorator dari antarmuka

export interface ComponentInterface = {
    selector: string,
    html: string
}
export const Component = mixinDecoratorFactory<ComponentInterface>();

Dan begitulah cara saya menggunakannya:

@Component({
    html: "<div> Some Text </div>",
    selector: "app-test"
})
export class Test extends HTMLElement {
    test = "test test"
    constructor() {
        super()
        console.log("inner;     test:", this.test)
        console.log("inner;     html:", this.html)
        console.log("inner; selector:", this.selector)
    }
}

export interface Test extends HTMLElement, ComponentInterface {}
window.customElements.define(Test.prototype.selector, Test)


const test = new Test();
console.log("outer;     test:", test.test)
console.log("outer;     html:", test.html)
console.log("outer; selector:", test.selector)

Triknya adalah juga membuat antarmuka dengan nama yang sama dengan kelas untuk membuat deklarasi gabungan.
Masih kelas hanya ditampilkan sebagai tipe Test tetapi pemeriksaan dari TypeScript berfungsi.
Jika Anda akan menggunakan dekorator tanpa @ -Notasi dan cukup memanggilnya sebagai Fungsi, Anda akan mendapatkan Tipe-persimpangan yang tepat tetapi kehilangan kemampuan pemeriksaan tipe di dalam kelas itu sendiri karena Anda tidak dapat menggunakan trik antarmuka lagi dan itu terlihat lebih jelek. Misalnya:

let Test2Comp = Component({
    html: "<div> Some Text 2 </div>",
    selector: "app-test2"
}) (
class Test2 extends HTMLElement {
    test = "test test"
    constructor() {
        super()
        console.log("inner;     test:", this.test)
        console.log("inner;     html:", this.html)     // no
        console.log("inner; selector:", this.selector) // no
    }
})

interface Test2 extends HTMLElement, ComponentInterface {} //no
window.customElements.define(Test2Comp.prototype.selector, Test2Comp)

const test2 = new Test2Comp();
console.log("outer;     test:", test2.test)
console.log("outer;     html:", test2.html)
console.log("outer; selector:", test2.selector)

Apa yang Anda katakan tentang pendekatan ini? Itu tidak indah tetapi bekerja sejauh solusi.

Apakah ada kemajuan dalam masalah ini? Ini sepertinya fitur yang sangat, sangat kuat yang akan membuka banyak kemungkinan yang berbeda. Saya menganggap masalah ini sebagian besar sudah basi sekarang karena dekorator masih eksperimental?

Akan sangat menyenangkan memiliki pernyataan resmi tentang @andy-ms @ahejlsberg @sandersn ini. 🙏

Kami mungkin tidak akan melakukan apa pun di sini sampai dekorator selesai.

@DanielRosenwasser - apa artinya "diselesaikan", di sini? Apakah Tahap 3 di TC39 memenuhi syarat?

Saya tidak menyadari mereka sampai sejauh itu! Kapan mereka mencapai tahap 3? Apakah itu baru-baru ini?

Mereka belum; Saya yakin mereka siap untuk maju dari Tahap 2 ke Tahap 3 lagi pada pertemuan TC39 Januari.

Anda dapat mengawasi agenda untuk detailnya.

Benar (walaupun saya tidak bisa memastikannya untuk kemajuan di bulan Januari). Saya hanya bertanya apakah Tahap 3 akan memenuhi syarat ketika mereka sampai di sana.

Menurut saya, proposal dekorator masih memiliki banyak masalah serius, jadi jika maju ke tahap 3 pada bulan Januari, akan membuat kesalahan lagi seperti proposal bidang kelas yang bermasalah dan proposal global ini.

@hax bisa Anda jelaskan?
Saya benar-benar menginginkan ini dan menemukan kurangnya komunikasi tentang masalah ini menyedihkan dan sayangnya belum pernah mendengar tentang masalah tersebut.

@AllNamesRTaken Periksa daftar masalah proposal dekorator. Misalnya, argumen export sebelum/sesudah dekorator adalah pemblokiran tahap 3.

Ada juga beberapa perubahan yang diusulkan seperti mendesain ulang API yang menurut saya berarti proposal tersebut tidak stabil untuk tahap 3.

Dan juga terkait dengan proposal bidang kelas yang bermasalah. Meskipun bidang kelas telah mencapai tahap 3, ada terlalu banyak masalah. Masalah besar yang saya khawatirkan adalah meninggalkan banyak masalah kepada dekorator (misalnya, protected ) dan itu buruk untuk kedua proposal.

Perhatikan, saya bukan delegasi TC39, dan beberapa delegasi TC39 tidak pernah menyetujui komentar saya tentang status terkini dari banyak masalah. (Terutama saya memiliki pendapat yang kuat bahwa proses TC39 saat ini memiliki kegagalan besar pada banyak masalah kontroversial. Menunda beberapa masalah ke dekorator tidak pernah benar-benar menyelesaikan masalah, hanya membuat proposal dekorator lebih rapuh.)

Saya pikir hal-hal itu akan diketahui dan proposalnya akan baik-baik saja, tetapi saya lebih suka kita tidak membahas status proposal di sini.

Saya pikir tahap 3 dengan kepercayaan diri yang cukup atau tahap 4 mungkin adalah tempat kami menerapkan proposal baru, dan kemudian kami dapat melihat masalah ini.

Terima kasih Daniel! Tahap 3 biasanya menyiratkan kepercayaan yang kuat pada 4, jadi semoga saja untuk pertemuan Januari.

Dan terima kasih telah memulai diskusi dekorator di sini. Sungguh aneh tingkat kemarahan dan pelepasan sepeda yang disebabkan oleh fitur ini. Saya belum pernah melihat yang seperti itu 😂

sebagai catatan ada permintaan fitur tentang hal yang sama: https://github.com/Microsoft/TypeScript/issues/8545

cukup mengganggu TypeScript mendukung fitur ini sampai batas tertentu ketika Anda mengkompilasi dari JavaScript (https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#better-handling-for-namespace-patterns-in -js-file):

// javascript via typescript
var obj = {};
obj.value = 1;
console.log(obj.value);

dan bahkan untuk kode TypeScript itu sendiri tetapi hanya ketika datang ke fungsi (!)(https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#properties-declarations-on-functions):

function readImage(path: string, callback: (err: any, image: Image) => void) {
    // ...
}

readImage.sync = (path: string) => {
    const contents = fs.readFileSync(path);
    return decodeImageSync(contents);
}

@DanielRosenwasser @arackaf Ada kabar tentang proposal pindah ke Tahap 3? Saya mencari untuk membangun perpustakaan yang menambahkan fungsi prototipe ke kelas saat menggunakan dekorator.

Mereka tidak maju pada pertemuan TC39 terakhir; mereka mungkin akan maju lagi pada pertemuan berikutnya. Namun, itu tidak jelas; ada banyak di udara tentang proposal pada saat ini.

Orang-orang di sini yang tertarik akan disarankan untuk melacak proposal itu sendiri – yang akan menjaga kebisingan bagi kita yang menonton utas serta untuk pengelola TS.

ada update untuk yang satu ini?

solusi (sudut :)) https://stackblitz.com/edit/iw-ts-extends-with-fakes?file=src%2Fapp%2Fextends-with-fakes.ts

import { Type } from '@angular/core';

export function ExtendsWithFakes<F1>(): Type<F1>;
export function ExtendsWithFakes<F1, F2>(): Type<F1 & F2>;
export function ExtendsWithFakes<F1, F2, F3>(): Type<F1 & F2 & F3>;
export function ExtendsWithFakes<F1, F2, F3, F4>(): Type<F1 & F2 & F3 & F4>;
export function ExtendsWithFakes<F1, F2, F3, F4, F5>(): Type<F1 & F2 & F3 & F4 & F5>;
export function ExtendsWithFakes<RealT, F1>(realTypeForExtend?: Type<RealT>): Type<RealT & F1>;
export function ExtendsWithFakes<RealT, F1, F2>(realTypeForExtend?: Type<RealT>): Type<RealT & F1 & F2>;
export function ExtendsWithFakes<RealT, F1, F2, F3>(realTypeForExtend?: Type<RealT>): Type<RealT & F1 & F2 & F3>;
export function ExtendsWithFakes<RealT, F1, F2, F3, F4>(
    realTypeForExtend?: Type<RealT>
): Type<RealT & F1 & F2 & F3 & F4>;
export function ExtendsWithFakes<RealT, F1, F2, F3, F4, F5>(
    realTypeForExtend?: Type<RealT>
): Type<RealT & F1 & F2 & F3 & F4 & F5> {
    if (realTypeForExtend) {
        return realTypeForExtend as Type<any>;
    } else {
        return class {} as Type<any>;
    }
}

interface IFake {
    fake(): string;
}

function UseFake() {
    return (target: Type<any>) => {
        target.prototype.fake = () => 'hello fake';
    };
}

class A {
    a() {}
}

class B {
    b() {}
}

@UseFake()
class C extends ExtendsWithFakes<A, IFake, B>(A) {
    c() {
        this.fake();
        this.a();
        this.b(); // failed at runtime
    }
}

Apa pendapat Anda tentang solusi mudah "sementara" ini?
https://stackoverflow.com/a/55520697/1053872

Mobx-state-tree menggunakan pendekatan serupa dengan fungsi cast() mereka. Rasanya tidak enak tapi... Itu juga tidak terlalu menggangguku. Sangat mudah untuk mengambil setiap kali sesuatu yang lebih baik datang.

Saya hanya ingin bisa melakukan sesuatu seperti ini ❤️
Bukankah ini kekuatan yang seharusnya dimiliki para dekorator?

class B<C = any> {}

function ChangeType<T>(to : T) : (from : any) => T;
function InsertType<T>(from : T) : B<T>;

@ChangeType(B)
class A {}

// A === B

// or

<strong i="7">@InsertType</strong>
class G {}

// G === B<G>

const g = new G(); // g : B<G>

A === B // equals true

const a : B = new A();  // valid
const b = new B();

typeof a === typeof b // valid

Saya menghabiskan cukup banyak waktu untuk membuat solusi untuk mengetik properti kelas yang didekorasi. Saya tidak berpikir itu akan menjadi solusi yang layak untuk skenario di utas ini, tetapi Anda mungkin menemukan beberapa bagian berguna. Jika Anda tertarik, Anda dapat memeriksa posting saya di Medium

Adakah pembaruan tentang masalah ini?

Bagaimana kita melakukannya?

Apa akibat dari masalah ini, bagaimana cara kerjanya sekarang?

Anda dapat menggunakan kelas mixin untuk mendapatkan efek itu
https://mariusschulz.com/blog/mixin-classes-in-typescript

@Bnaya yang sama sekali bukan yang kita inginkan sekarang;).
Dekorator membiarkan kami menghindari pembuatan kelas yang tidak pernah kami maksudkan untuk digunakan seperti yang kami lakukan di Jawa sekolah lama. Dekorator akan memungkinkan arsitektur komposisi yang sangat bersih dan rapi di mana kelas dapat terus melakukan fungsionalitas inti dan memiliki fungsionalitas umum ekstra yang disusun di dalamnya. Ya mixin bisa melakukannya tapi itu bandaid.

Proposal dekorator berubah secara signifikan dalam beberapa bulan terakhir - sangat tidak mungkin tim TS akan menginvestasikan waktu ke implementasi saat ini yang akan segera tidak kompatibel™.

Saya akan merekomendasikan menonton sumber daya berikut:

Proposal dekorator: https://github.com/tc39/proposal-decorators
Peta jalan TypeScript: https://github.com/microsoft/TypeScript/wiki/Roadmap

@Bnaya yang sama sekali bukan yang kita inginkan sekarang;).
Dekorator membiarkan kami menghindari pembuatan kelas yang tidak pernah kami maksudkan untuk digunakan seperti yang kami lakukan di Jawa sekolah lama. Dekorator akan memungkinkan arsitektur komposisi yang sangat bersih dan rapi di mana kelas dapat terus melakukan fungsionalitas inti dan memiliki fungsionalitas umum ekstra yang disusun di dalamnya. Ya mixin bisa melakukannya tapi itu bandaid.

Ini bukan pencampuran, tapi kelas.
Bukan campuran hal-hal yang buruk itu
Ketika operator pipa akan tiba, sintaksnya juga akan masuk akal

Anda dapat menggunakan kelas mixin untuk mendapatkan efek itu

Mixin kelas-pabrik bisa menjadi masalah di TypeScript ; bahkan jika tidak, mereka sangat boilerplatey dengan mengharuskan Anda untuk membungkus kelas dalam fungsi untuk mengkonversi ke mixin, yang kadang-kadang Anda tidak bisa begitu saja jika Anda mengimpor kelas pihak ke-3.

Berikut ini jauh lebih baik, lebih sederhana, lebih bersih, dan berfungsi dengan kelas pihak ke-3 yang diimpor. Saya membuatnya berfungsi dengan baik dalam JavaScript biasa tanpa transpilasi selain untuk mentranspilasikan dekorator gaya lama, tetapi class tetap benar-benar asli class es:

class One {
    one = 1
    foo() { console.log('foo', this.one) }
}

class Two {
    two = 2
    bar() { console.log('bar', this.two) }
}

class Three extends Two {
    three = 3
    baz() { console.log('baz', this.three, this.two) }
}

@with(Three, One)
class FooBar {
    yeah() { console.log('yeah', this.one, this.two, this.three) }
}

let f = new FooBar()

console.log(f.one, f.two, f.three)
console.log(' ---- call methods:')

f.foo()
f.bar()
f.baz()
f.yeah()

Dan output di Chrome adalah:

1 2 3
 ---- call methods:
foo 1
bar 2
baz 3 2
yeah 1 2 3

Ini sepenuhnya memberi Anda apa yang dilakukan mixin kelas-pabrik, tanpa semua boilerplate. Pembungkus fungsi di sekitar kelas Anda tidak lagi diperlukan.

Namun, seperti yang Anda ketahui, dekorator tidak dapat mengubah tipe kelas yang ditentukan. 😢.

Jadi, untuk saat ini, saya dapat melakukan hal berikut, dengan satu-satunya peringatan adalah bahwa anggota yang dilindungi tidak dapat diwarisi:

// `multiple` is similar to `@with`, same implementation, but not a decorator:
class FooBar extends multiple(Three, One) {
    yeah() { console.log('yeah', this.one, this.two, this.three) }
}

Masalah dengan kehilangan anggota yang dilindungi diilustrasikan dengan dua contoh implementasi multiple ini (sejauh jenisnya, tetapi implementasi runtime dihilangkan untuk singkatnya):

  • contoh tanpa kesalahan , karena semua properti di kelas gabungan bersifat publik.
  • contoh dengan kesalahan (di bagian paling bawah), karena tipe yang dipetakan mengabaikan anggota yang dilindungi, dalam hal ini membuat Two.prototype.two tidak dapat diwariskan.

Saya sangat menginginkan cara untuk memetakan jenis termasuk anggota yang dilindungi.

Ini akan sangat berguna bagi saya karena saya memiliki dekorator yang memungkinkan kelas dipanggil sebagai fungsi.

Ini bekerja:

export const MyClass = withFnConstructor(class MyClass {});

Ini tidak berfungsi:

<strong i="10">@withFnConstructor</strong>
export class MyClass {}

Solusi yang tidak terlalu sulit untuk dibersihkan ketika fitur ini muncul dapat dilakukan dengan menggunakan penggabungan deklarasi dan file definisi tipe kustom.

Sudah memiliki ini:

// ClassModifier.ts
export interface Mod {
  // ...
}
// The decorator
export function ClassModifier<Args extends any[], T>(target: new(...args: Args) => T): new(...args: Args) => T & Mod {
  // ...
}
// MyClass.ts
<strong i="9">@ClassModifier</strong>
export MyClass {
  // ...
}

Tambahkan file berikut (file yang akan dihapus setelah fitur keluar):

// MyClass.d.ts
import { MyClass } from './MyClass';
import { Mod } from './ClassModifier';

declare module './MyClass' {
  export interface MyClass extends Mod {}
}

Solusi lain yang mungkin

declare class Extras { x: number };
this.Extras = Object;

class X extends Extras {
   constructor() {  
      super(); 
      // a modification to object properties that ts will not pick up
      Object.defineProperty(this, 'x', {value: 3});
   }
}

const a = new X()
a.x // 3

Dimulai pada tahun 2015 dan masih dalam proses. Ada banyak lagi masalah terkait dan bahkan artikel seperti ini https://medium.com/p/caf24aabcb59/responses/show , mencoba menunjukkan solusi (yang sedikit meretas, tetapi membantu)

Apakah mungkin bagi seseorang untuk menjelaskan hal ini. Apakah itu bahkan dipertimbangkan untuk diskusi internal?

tl;dr - fitur ini macet di TC39. Tidak bisa menyalahkan orang-orang TS untuk ini sama sekali. Mereka tidak akan menerapkan sampai standar.

Tapi ini Microsoft, mereka bisa mengimplementasikannya dan kemudian mengatakan kepada orang-orang standar - "lihat, orang-orang sudah menggunakan versi kami " :trollface:

Masalahnya di sini adalah bahwa proposal dekorator telah berubah _secara signifikan_ (dengan cara yang tidak kompatibel) sejak TypeScript mengimplementasikan dekorator. Ini akan mengarah pada situasi yang menyakitkan ketika dekorator baru diselesaikan dan diimplementasikan, karena beberapa orang mengandalkan perilaku dekorator dan semantik TypeScript saat ini. Saya sangat curiga bahwa tim TypeScript ingin menghindari tindakan masalah ini karena hal itu akan mendorong penggunaan lebih lanjut dari dekorator non-standar saat ini, yang pada akhirnya akan membuat transisi ke implementasi dekorator baru menjadi lebih menyakitkan. Pada dasarnya, saya pribadi tidak melihat ini terjadi.

Kebetulan, saya baru-baru ini membuat #36348, yang merupakan proposal untuk fitur yang akan memberikan solusi yang cukup solid. Jangan ragu untuk melihat/memberi umpan balik tentang/menindasnya. 🙂.

tl;dr - fitur ini macet di TC39. Tidak bisa menyalahkan orang-orang TS untuk ini sama sekali. Mereka tidak akan menerapkan sampai standar.

Mengingat fakta ini, mungkin bijaksana bagi tim pengembangan untuk menulis penjelasan singkat dan pembaruan status, dan mengunci percakapan ini hingga TC39 meneruskannya ke tahap yang diperlukan?

Apakah ada yang membaca ini memiliki bobot dalam masalah ini?

Sebenarnya, hampir semua percakapan terkait dekorator menggantung di udara. Contoh: https://github.com/Microsoft/TypeScript/issues/2607

Mendapatkan kejelasan tentang masa depan dekorator akan sangat membantu, bahkan jika ini tentang meminta kontributor untuk mengimplementasikan fungsionalitas yang diperlukan. Tanpa panduan yang jelas, masa depan dekorator menjadi kabur

Saya akan senang jika tim TypeScript dapat mengambil peran proaktif untuk proposal dekorator, mirip dengan bagaimana mereka membantu menyelesaikan rangkaian opsional. Saya juga ingin membantu, tetapi belum bekerja dalam pengembangan bahasa dan oleh karena itu saya tidak yakin bagaimana cara terbaik untuk melakukannya :-/

Saya tidak dapat berbicara mewakili seluruh tim, dan TypeScript sebagai sebuah proyek - tetapi saya pikir tidak mungkin kita akan melakukan pekerjaan dekorator baru sampai diselesaikan dan dikonfirmasi, mengingat bagaimana implementasi dekorator telah menyimpang sekali dari implementasi TypeScript.

Proposal masih dalam pengembangan aktif, dan muncul di TC39 minggu ini https://github.com/tc39/proposal-decorators/issues/305

@orta dalam hal ini saya pikir dokumen tentang dekorator kelas harus diperbarui. Ini menyatakan bahwa

Jika dekorator kelas mengembalikan nilai, itu akan menggantikan deklarasi kelas dengan fungsi konstruktor yang disediakan.

Juga, contoh berikut dari dokumen tampaknya menyiratkan bahwa tipe instance Greeter akan memiliki properti newProperty , yang tidak benar:

function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
    return class extends constructor {
        newProperty = "new property";
        hello = "override";
    }
}

<strong i="13">@classDecorator</strong>
class Greeter {
    property = "property";
    hello: string;
    constructor(m: string) {
        this.hello = m;
    }
}

console.log(new Greeter("world"));

Saya pikir perlu untuk menambahkan ke dokumen bahwa antarmuka kelas yang dikembalikan tidak akan menggantikan yang asli.

Menarik, saya menguji banyak TypeScript versi lama dan tidak dapat menemukan kesempatan di mana contoh kode itu berfungsi ( contoh dari 3.3.3 ) - jadi ya, saya setuju.

Saya telah membuat https://github.com/microsoft/TypeScript-Website/issues/443 - jika seseorang tahu cara membuat classDecorator itu memiliki tipe persimpangan eksplisit, silakan beri komentar di masalah itu

Saya berakhir di sini karena masalah dengan dokumentasi yang tercantum di atas memang membuat saya bingung. Meskipun akan lebih bagus jika jenis tanda tangan dari nilai yang dikembalikan dari dekorator dikenali oleh TS Compiler, sebagai pengganti perubahan yang lebih besar, saya setuju bahwa dokumentasi harus diklarifikasi.

Sebagai catatan, ini adalah versi sederhana dari apa yang saya coba dalam format dokumen terkait dari @orta 's PR:

interface HasNewProperty {
  newProperty: string;
}

function classDecorator<T extends { new (...args: any[]): {} }>(
  constructor: T
) {
  return class extends constructor implements HasNewProperty {
    newProperty = "new property";
    hello = "override";
  };
}

<strong i="8">@classDecorator</strong>
class Greeter {
  property = "property";
  hello: string;
  constructor(m: string) {
    this.hello = m;
  }
}

console.log(new Greeter("world"));

// Alas, this line makes the compiler angry because it doesn't know
// that Greeter now implements HasNewProperty
console.log(new Greeter("world").newProperty);
Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

sandersn picture sandersn  ·  265Komentar

metaweta picture metaweta  ·  140Komentar

quantuminformation picture quantuminformation  ·  273Komentar

jonathandturner picture jonathandturner  ·  147Komentar

isiahmeadows picture isiahmeadows  ·  134Komentar