Typescript: طلب: طفرة ديكور الفصل

تم إنشاؤها على ٢٠ سبتمبر ٢٠١٥  ·  231تعليقات  ·  مصدر: microsoft/TypeScript

إذا تمكنا من جعل هذا يتحقق بشكل صحيح ، فسنحصل على دعم مثالي للخلطات الخالية من المتغيرات:

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

التعليق الأكثر فائدة

قد يكون نفس الشيء مفيدًا للطرق:

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

من المفترض أن يقوم المصمم غير المتزامن بتغيير نوع الإرجاع إلى Promise<any> .

ال 231 كومينتر

قد يكون نفس الشيء مفيدًا للطرق:

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

من المفترض أن يقوم المصمم غير المتزامن بتغيير نوع الإرجاع إلى Promise<any> .

Gaelan ، هذا بالضبط ما نحتاجه هنا! من شأنه أن يجعل الخلطات طبيعية للعمل معها.

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 لهذا. على الرغم من أنني أعلم أن هذا صعب التنفيذ ، وربما يكون من الصعب التوصل إلى اتفاق بشأن دلالات طفرات الديكور.

+1

إذا كانت الفائدة الأساسية من ذلك هي تقديم أعضاء إضافيين لتوقيع النوع ، فيمكنك فعل ذلك بالفعل من خلال دمج الواجهة:

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

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

new Foo().foo();

إذا كان المصمم وظيفة فعلية يحتاج المترجم إلى استدعاؤها من أجل تغيير الفصل بشكل إلزامي ، فلا يبدو هذا كشيء اصطلاحي يجب القيام به في لغة آمنة من النوع ، IMHO.

masaeedu هل تعرف أي حل بديل لإضافة عضو ثابت إلى الفصل المزين؟

تضمين التغريدة ها أنت ذا:

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

قد يكون من المفيد أيضًا أن تكون قادرًا على تقديم خصائص _متعددة_ إلى فئة ما عند تزيين طريقة ما (على سبيل المثال ، المساعد الذي يولد أداة ضبط مرتبطة بالحاصل ، أو شيء من هذا القبيل)

تأخذ عمليات الكتابة التفاعلية والإعادة لـ connect مكونًا وتعيد مكونًا معدلًا لا تشتمل دعائمه على الدعائم المتصلة التي تم استلامها من خلال redux ، ولكن يبدو أن TS لا يتعرف على تعريف connect على أنه مصمم بسبب هذه القضية. هل يمتلك أحد حلا للمشكة؟

أعتقد أن تعريف النوع ClassDecorator يحتاج إلى التغيير.

حاليًا هو declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void; . ربما يمكن تغييره إلى

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;

من الواضح أن التسمية سيئة وليس لدي أي فكرة عما إذا كان هذا النوع من الأشياء سيعمل (أنا فقط أحاول تحويل تطبيق Babel إلى نص مكتوب وأقوم بضرب هذا).

joyt هل يمكنك توفير إعادة بناء ملعب للمشكلة؟ لا أستخدم رد الفعل والإعادة ، ولكن كما ذكرت من قبل ، أعتقد أنه يمكن الإعلان عن أي ملحقات ترغب فيها على شكل نوع باستخدام دمج الواجهة.

masaeedu هنا تفصيل أساسي للأجزاء المتحركة ..

يوفر المصمم بشكل أساسي مجموعة من الدعائم لمكون React ، لذا فإن النوع العام لمصمم الديكور هو مجموعة فرعية من المكون المزخرف ، وليس مجموعة شاملة.

لست متأكدًا مما إذا كان هذا مفيدًا ، ولكنك حاولت تجميع عينة غير قابلة للتشغيل لتظهر لك الأنواع الموجودة في اللعب.

// 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>

إذا كنت تريد مثالاً عمليًا كاملاً ، أقترح سحب https://github.com/jaysoo/todomvc-redux-react-typescript أو مشروع آخر يتفاعل / redux / typecript.

وفقًا لـ https://github.com/wycats/javascript-decorators#class -declare ، ما أفهمه هو أن declare type WrappingClassDecorator = <TFunction extends Function, TDecoratorFunction extends Function>(target: TFunction) => TDecoratorFunction; المقترح غير صالح.

المواصفات تقول:

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

يترجم إلى:

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

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

لذلك إذا فهمتها بشكل صحيح ، يجب أن يكون ما يلي صحيحًا:

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

تضمين التغريدة

على سبيل المثال الخاص بك:

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

أفترض أنك تعني أن F تُرجع فئة تتوافق مع X (وليست مثيلًا لـ X )؟ على سبيل المثال:

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

لهذه الحالة ، يجب أن تكون التأكيدات:

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

تم تغيير Foo الموجود في نطاق عبارات let بواسطة مصمم الديكور. لم يعد بالإمكان الوصول إلى الأصل Foo . إنه مكافئ فعليًا لـ:

let Foo = F(class Foo {});

nevir نعم ، أنت على حق. شكرا للتوضيح.

في ملاحظة جانبية ، يبدو أن إيقاف تشغيل الفحص لإبطال أنواع الفئات المتحولة أمر سهل نسبيًا:

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:
         }

لكنني لست على دراية كافية لجعل المترجم يخرج تعريفات النوع الصحيحة للفئة المتحولة. لدي الاختبار التالي:

الاختبارات / الحالات / المطابقة / الديكور / الدرجة / decoratorOnClass10.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();

يقوم بإنشاء اختبارات / خطوط أساسية / أنواع محلية / decoratorOnClass10

=== 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

كنت أتوقع
>C: typeof C ليكون >C: typeof X | typeof Y

للراغبين في رد فعل-redux connect كدراسة حالة لهذه الميزة ، قمت بتقديم https://github.com/DefinitelyTyped/DefinitelyTyped/issues/9951 لتتبع المشكلة في مكان واحد.

لقد قرأت جميع التعليقات حول هذه المسألة ووجدت فكرة مفادها أن توقيع المصمم لا يُظهر في الواقع ما يمكنه فعله بالفصل المغلف.

ضع في اعتبارك هذا:

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

يجب كتابتها بهذه الطريقة:
declare function decorator<T>(target: T): Wrapper<T>;

لكن هذا التوقيع لا يخبرنا أن المصمم أضاف أشياء جديدة إلى النموذج الأولي للهدف.

من ناحية أخرى ، هذا لا يخبرنا أن المصمم قد أعاد بالفعل غلافًا:
declare function decorator<T>(target: T): T & { someMethod: () => void };

أي أخبار عن هذا؟ سيكون هذا قويًا جدًا في البرمجة الوصفية!

ماذا عن نهج أبسط لهذه المشكلة؟ بالنسبة للفئة المزخرفة ، نقوم بربط اسم الفئة بالقيمة المرجعة لمصمم الديكور ، كسكر نحوي.

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.

من ناحية التنفيذ ، سيقدم هذا رمزًا اصطناعيًا واحدًا للفئة المزخرفة. واسم الفئة الأصلي مرتبط فقط بنطاق جسم الفئة.

HerringtonDarkholme أعتقد أنه سيكون نهجًا براغماتيًا لطيفًا من شأنه أن يوفر معظم التعبيرات المرغوبة. فكرة عظيمة!

أنا بالتأكيد أريد أن أرى هذا يومًا ما

غالبًا ما أكتب فصلًا دراسيًا لـ Angular 2 أو Aurelia ، يبدو كالتالي:

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

ما أريد كتابته هو شيء من هذا القبيل
الديكور / 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;
    }
  }
}

وبعد ذلك يمكنني تطبيقه بشكل عام مثل

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) { }
}

مع عدم فقدان الأمان. سيكون هذا نعمة لكتابة كود نظيف ومعبر.

إحياء هذه القضية.

الآن بعد أن خرج # 13743 وأصبح دعم mixin باللغة ، فهذه ميزة مفيدة للغاية.

HerringtonDarkholme أقل ملاءمة لهذه الحالة ، ومع ذلك ، فإن الاضطرار إلى الإعلان عن نوع الإرجاع للديكور يفقد بعض الميزات الديناميكية ...

ahejlsberg ، mhegazy هل تعتقد أن هذا ممكن؟

لدي سيناريو استخدام آخر لست متأكدًا من أنه تمت تغطيته بعد في هذه المحادثة ولكن ربما يقع تحت نفس المظلة.

أرغب في تطبيق مزخرف طريقة يغير نوع الطريقة بالكامل (ليس نوع الإرجاع أو المعلمات ولكن الوظيفة بأكملها). على سبيل المثال

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

من الممكن تمامًا في JavaScript ، وهذه ليست المشكلة بوضوح ، ولكن في TypeScript أحتاج إلى asyncTask decorator لتغيير نوع الطريقة المزخرفة من () => Promise<void> إلى AsyncTask<() => Promise<void>> .

هل أنت متأكد من أن هذا غير ممكن الآن وربما يقع تحت مظلة هذه المشكلة؟

codeandcats مثالك هو نفس حالة الاستخدام التي أنا هنا من أجلها!

مرحبًا ohjames ، سامحني ، لدي مشكلة في التذمر من مثالك ، هل هناك فرصة لإعادة الكتابة في شيء يعمل كما هو الحال في الملعب؟

أي تقدم في هذا؟ لقد كان هذا في رأسي طوال اليوم ، غير مدرك لهذه المشكلة ، ذهبت لتنفيذه فقط لأكتشف أن المترجم لا يلتقطها. لدي مشروع يمكن أن يستخدم حل تسجيل أفضل ، لذلك كتبت مفردًا سريعًا للتوسع لاحقًا إلى مسجل كامل كنت سأقوم بإرفاقه بالفصول الدراسية عبر مصمم مثل

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

وكتبت الكود اللازم لذلك

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

وخاصية logger موجودة بالتأكيد في وقت التشغيل ولكن للأسف لم يتم التقاطها من قبل المترجم.

أرغب في رؤية بعض الحلول لهذه المشكلة ، خاصة وأن إنشاء وقت التشغيل مثل هذا يجب أن يكون قادرًا تمامًا على تمثيله بشكل صحيح في وقت الترجمة.

انتهى بي الأمر بالاستقرار لمصمم ديكور منزلي لأخذي مني الآن:

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

وربطها بفصول مثل

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

ولكن هذا أكثر بكثير من نموذج معياري لكل فئة باستخدام المسجل من مصمم بسيط من فئة @loggable . أفترض أنه يمكنني الكتابة بشكل عملي مثل (this as Loggable<this>).logger لكن هذا أيضًا بعيد جدًا عن المثالية ، خاصة بعد القيام بذلك بضع مرات. سيصبح متعبًا بسرعة كبيرة.

اضطررت إلى استخدام TS لتطبيق كامل لأنني لم أتمكن من الحصول على https://github.com/jeffijoe/mobx-task العمل مع المصممين. آمل أن يتم معالجة هذا قريبًا. 😄

إنه أمر مزعج للغاية في نظام Angular 2 البيئي حيث يتم التعامل مع مصممي الديكور و TypeScript كمواطنين من الدرجة الأولى. ومع ذلك ، في اللحظة التي تحاول فيها إضافة خاصية باستخدام المصمم ، يقول مترجم TypeScript لا. كنت أعتقد أن فريق Angular 2 سيبدي بعض الاهتمام بهذه المشكلة.

zajrik ، يمكنك إنجاز ما تريد باستخدام مزيج الصفوف التي تم دعمها بالكتابة المناسبة منذ TS 2.2:

حدد مزيج Loggable الخاص بك على النحو التالي:

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

وبعد ذلك يمكنك استخدامه بعدة طرق. إما في بند extends لإعلان الفئة:

class Foo {
  superProperty: string;
}

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

يعرف TS أن مثيلات LoggableFoo لها superProperty و logger و subProperty :

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

يمكنك أيضًا استخدام mixin كتعبير يُرجع فئة الخرسانة التي تريد استخدامها:

const LoggableFoo = Loggable(Foo);

يمكنك أيضًا _يمكنك_ استخدام فئة mixin كمصمم ديكور ، ولكن لها بعض الدلالات المختلفة قليلاً ، خاصة تلك الفئات الفرعية لفصلك ، بدلاً من السماح لفصلك بفئة فرعية له.

تتميز الخلطات الصفية بالعديد من المزايا مقارنة بمصممي الديكور ، IMO:

  1. إنهم ينشئون طبقة عليا جديدة ، بحيث يكون للفصل الذي تطبقهم عليه تغيير لتجاوزهم
  2. يكتبون check الآن ، دون أي ميزات إضافية من TypeScript
  3. إنها تعمل بشكل جيد مع استدلال النوع - ليس عليك كتابة القيمة المرجعة لوظيفة mixin
  4. إنها تعمل بشكل جيد مع التحليل الثابت ، لا سيما الانتقال إلى التعريف - القفز إلى تنفيذ logger يأخذك إلى mixin _implementation_ ، وليس الواجهة.

@ justinfagnani لم أفكر حتى في الخلطات لهذا شكرا لك. سأمضي قدمًا وأكتب مزيج Loggable الليلة لأجعل تركيب مرفق المسجل الخاص بي أجمل قليلاً. إن المسار extends Mixin(SuperClass) هو المفضل لدي لأنه الطريقة التي استخدمت بها mixins حتى الآن منذ إصدار TS 2.2.

أنا أفضل فكرة بناء جملة الديكور على الخلطات ، ومع ذلك ، ما زلت آمل أن يكون هناك بعض الحل لهذه المشكلة المحددة. إن القدرة على إنشاء خلطات خالية من القوالب باستخدام أدوات الزخرفة ستكون نعمة كبيرة لكود أكثر نظافة ، في رأيي.

zajrik سعيد لأن الاقتراح ساعدني ، آمل

ما زلت لا أفهم تمامًا كيف أن الخلطات تحتوي على مواد أكثر من المصممين. إنهما متماثلان تقريبًا في الوزن النحوي:

فئة Mixin:

class LoggableFoo extends Loggable(Foo) {}

مقابل الديكور:

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

في رأيي ، فإن mixin أكثر وضوحًا بشأن نواياه: إنه يولد طبقة فائقة ، والفئات الفائقة تحدد أعضاء الفصل ، لذلك من المحتمل أن يكون mixin هو تحديد الأعضاء أيضًا.

سيتم استخدام المصممين للعديد من الأشياء التي لا يمكنك افتراض أنها تحدد أو لا تحدد الأعضاء. يمكن أن يكون ببساطة تسجيل الفصل لشيء ما ، أو ربط بعض البيانات الوصفية به.

لكي نكون منصفين ، أعتقد أن ما يريده zajrik هو:

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

والذي لا يمكن إنكاره ، إذا كان أقل من ذلك ، فهو أقل متداخلاً.

ومع ذلك ، أنا أحب حل mixin. ما زلت أنسى أن الخلطات شيء.

إذا كان كل ما يهمك هو إضافة خصائص إلى الفصل الحالي ، فإن المزيجات تعادل بشكل أساسي المصممين الذين لديهم مصدر إزعاج كبير ... إذا لم يكن لدى فصلك فئة فائقة بالفعل ، فأنت بحاجة إلى إنشاء فئة فائقة فارغة لاستخدامها. كما يبدو بناء الجملة أثقل بشكل عام. كما أنه ليس من الواضح ما إذا كانت الخلطات البارامترية مدعومة (مسموح extends Mixin(Class, { ... }) ).

justinfagnani في قائمة الأسباب ، النقاط 2-4 هي في الواقع أوجه قصور في TypeScript وليست من مزايا mixins. لا تنطبق في عالم JS.

أعتقد أنه يجب علينا جميعًا أن نكون واضحين في أن الحل القائم على mixin لمشكلة OPs قد يتضمن إضافة فئتين إلى سلسلة النموذج الأولي ، أحدهما عديم الفائدة. هذا يعكس الاختلافات الدلالية لمصممي mixins Vs ، ومع ذلك ، تمنحك mixins فرصة لاعتراض سلسلة الفئة الأصلية. ومع ذلك ، 95٪ من الوقت ليس هذا ما يريد الناس القيام به ، فهم يريدون تزيين هذا الفصل. في حين أن استخدامات mixins لها استخدامات محدودة ، أعتقد أن الترويج لها كبديل لمصممي الديكور وفصول الرتب الأعلى أمر غير مناسب من الناحية المعنوية.

المزيجات تعادل بشكل أساسي المصممين الذين لديهم مصدر إزعاج كبير ... إذا لم يكن فصلك يحتوي بالفعل على فئة فائقة ، فأنت بحاجة إلى إنشاء فئة فائقة فارغة لاستخدامها

هذا ليس صحيحًا بالضرورة:

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

class Foo extends Mixin() {}

كما يبدو بناء الجملة أثقل بشكل عام.

أنا فقط لا أرى كيف يكون الأمر كذلك ، لذلك علينا أن نختلف.

كما أنه ليس من الواضح ما إذا كانت الخلطات البارامترية مدعومة (يُسمح بتوسيع Mixin (الفئة ، {...})).

هم كثيرا. Mixins هي مجرد وظائف.

في قائمة الأسباب ، النقاط 2-4 هي في الواقع أوجه قصور في TypeScript وليست مزايا للمزج. لا تنطبق في عالم JS.

هذه مشكلة في TypeScript ، لذا فهي تنطبق هنا. الديكور في عالم JS غير موجود بالفعل حتى الآن.

أعتقد أنه يجب علينا جميعًا أن نكون واضحين في أن الحل القائم على mixin لمشكلة OPs قد يتضمن إضافة فئتين إلى سلسلة النموذج الأولي ، أحدهما عديم الفائدة.

لست واضحا من أين تحصل على اثنين. إنها واحدة ، تمامًا مثلما قد يفعل المصممون ، ما لم يتم ترقيعها. وأي نموذج أولي غير مجدي؟ من المفترض أن يضيف تطبيق mixin خاصية / طريقة ، وهذا ليس عديم الفائدة.

هذا يعكس الاختلافات الدلالية لمصممي mixins Vs ، ومع ذلك ، تمنحك mixins فرصة لاعتراض سلسلة الفئة الأصلية. ومع ذلك ، 95٪ من الوقت ليس هذا ما يريد الناس القيام به ، فهم يريدون تزيين هذا الفصل.

لست متأكدًا من صحة هذا. عادةً عند تحديد فئة ، تتوقع أن تكون في أسفل التسلسل الهرمي للميراث ، مع القدرة على تجاوز طرق الطبقة الفائقة. يتعين على المصممين إما تصحيح الفصل الذي يعاني من العديد من المشكلات ، بما في ذلك عدم العمل مع super() ، أو تمديده ، وفي هذه الحالة لا يكون للفئة المزخرفة القدرة على تجاوز الامتداد. يمكن أن يكون هذا مفيدًا في بعض الحالات ، مثل أدوات الديكور التي تتجاوز كل طريقة محددة للفئة لتتبع الأداء / تصحيح الأخطاء ، ولكنها بعيدة كل البعد عن نموذج الوراثة المعتاد.

في حين أن استخدامات mixins لها استخدامات محدودة ، أعتقد أن الترويج لها كبديل لمصممي الديكور وفصول الرتب الأعلى أمر غير مناسب من الناحية المعنوية.

عندما يريد مطور إضافة أعضاء إلى سلسلة النموذج الأولي ، فإن الخلطات تكون مناسبة تمامًا من الناحية المعنوية. في كل حالة رأيت فيها شخصًا يريد استخدام المصممين للخلطات ، فإن استخدام مزيج الفصل سينجز نفس المهمة ، مع الدلالات التي يتوقعونها بالفعل من المصممين ، ومزيد من المرونة بسبب خاصية العمل مع المكالمات الفائقة ، و بالطبع يعملون الآن.

تعتبر Mixins غير مناسبة عندما تتناول حالة الاستخدام بشكل مباشر.

عندما يريد المطور إضافة أعضاء إلى سلسلة النموذج الأولي

هذه بالضبط وجهة نظري ، OP لا تريد إضافة أي شيء إلى سلسلة النموذج الأولي. إنه يريد فقط أن يغير فئة واحدة ، وفي الغالب عندما يستخدم الناس المصممين ، فليس لديهم حتى فئة الوالدين بخلاف الكائن. ولسبب ما ، لا يعمل Mixin(Object) في TypeScript لذا عليك إضافة فئة فارغة وهمية. الآن لديك سلسلة نموذج أولي من 2 (لا تشمل الكائن) عندما لا تحتاج إليها. بالإضافة إلى أن هناك تكلفة غير بسيطة لإضافة فئات جديدة إلى سلسلة النموذج الأولي.

بالنسبة للصيغة قارن Mixin1(Mixin2(Mixin3(Object, { ... }), {... }), {...}) . المعلمات لكل mixin بعيدة عن فئة mixin كما يمكن أن تكون. من الواضح أن بناء جملة Decorator أكثر قابلية للقراءة.

في حين أن بناء جملة المصمم في حد ذاته لا يقوم بالتحقق من الكتابة ، يمكنك فقط استخدام استدعاء الوظيفة العادية للحصول على ما تريد:

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

إنه أمر مزعج قليلاً أن تضطر إلى إعلان صفك على أنه const Foo = loggable(class { ، ولكن بصرف النظر عن ذلك ، فإن كل شيء يعمل.

ohjames (ccjustinfagnani) ، يجب أن تكون حذرًا عند توسيع عناصر مضمنة مثل Object (نظرًا لأنها تتعارض مع النموذج الأولي للفئة الفرعية في بعض الحالات): https://github.com/Microsoft/TypeScript/wiki/FAQ #why -doesnt-extending-built-ins-like-error-array-and-map-work

nevir yep ، لقد جربت بالفعل اقتراح justinfagnani باستخدام mixin مع معلمة افتراضية Object في الماضي مع TypeScript 2.2 ورفض tsc الكود.

ohjames لا يزال يعمل ، ما عليك سوى توخي الحذر بشأن النموذج الأولي في الحالة الافتراضية (انظر إدخال الأسئلة الشائعة).

على الرغم من ذلك ، من الأسهل عمومًا الاعتماد على سلوك tslib.__extend عند تجاوز null

هل هناك أي خطط لتركيز هذه المشكلة في خطوة التكرار التالية؟ فوائد هذه الميزة عالية للغاية عبر الكثير من المكتبات.

لقد واجهت هذه المشكلة للتو - أجبرتني على كتابة الكثير من التعليمات البرمجية غير الضرورية. سيكون حل هذه المشكلة بمثابة مساعدة كبيرة لأي إطار / مكتبة تعتمد على الديكور.

TomMarius كما ذكرت سابقًا ، فالفصول المغلفة بوظائف الزخرفة تكتب بالفعل التحقق بشكل صحيح ، لا يمكنك استخدام السكر البنيوي @ . بدلًا من القيام بما يلي:

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

ما عليك سوى القيام بما يلي:

const Foo = loggable(class { });

يمكنك أيضًا تكوين مجموعة من وظائف الديكور معًا قبل اختتام فصل دراسي بها. في حين أن جعل بناء الجملة السكر يعمل بشكل صحيح أمر ذو قيمة ، لا يبدو أن هذا يجب أن يكون نقطة ألم كبيرة مثل الأشياء.

masaeedu حقًا المشكلة ليست خارجية ولكنها دعم داخلي. إن القدرة على استخدام الخصائص التي يضيفها المصمم داخل الفصل نفسه دون أخطاء في الترجمة هي النتيجة المرجوة ، على الأقل بالنسبة لي. المثال الذي قدمته سيعطي فقط Foo النوع القابل للتسجيل ولكن لن يوفر النوع لتعريف الفئة نفسه.

zajrik يقوم مصمم الديكور بإرجاع فئة جديدة من فئة أصلية ، حتى عند استخدام بناء الجملة المدمج في @ . من الواضح أن JS لا تفرض النقاء ، لذا فأنت حر في تغيير الفئة الأصلية التي تم اجتيازها ، لكن هذا يتعارض مع الاستخدام الاصطلاحي لمفهوم الديكور. إذا كنت تقارن الوظائف بإحكام التي تضيفها عبر أدوات الزخرفة إلى الأجزاء الداخلية للفصل ، فقد تكون أيضًا خصائص داخلية.

هل يمكنك أن تعطيني مثالاً على حالة استخدام لفصل يستخدم واجهة برمجة تطبيقات مستهلكة داخليًا تمت إضافته في وقت لاحق عبر أدوات التزيين؟

مثال المسجل أعلاه هو مثال جيد للرغبة المشتركة للقدرة على معالجة الأجزاء الداخلية للفئة المزخرفة. (ومألوف لدى القادمين من لغات أخرى مع الزخرفة الصفية ؛ مثل بايثون )

ومع ذلك ، يبدو اقتراح mixin للفئة justinfagnani بديلاً جيدًا لهذه الحالة

إذا كنت تريد أن تكون قادرًا على تحديد العناصر الداخلية للفصل ، فإن الطريقة المنظمة للقيام بذلك ليست تصحيح الفصل ، أو تحديد فئة فرعية جديدة ، وكلاهما سيواجه TypeScript صعوبة في التفكير في سياق الفصل نفسها ، ولكن إما لتعريف الأشياء في الفصل نفسه ، أو إنشاء فئة فائقة جديدة تحتوي على الخصائص المطلوبة ، والتي يمكن لـ TypeScript التفكير بشأنها.

لا ينبغي للمصممين حقًا تغيير شكل الفصل بطريقة مرئية للفصل أو معظم المستهلكين. masaeedu هنا.

بينما ما تقوله صحيح ، فإن TypeScript ليس موجودًا لفرض ممارسات الترميز النظيفة ، ولكن لكتابة كود JavaScript بشكل صحيح ، ويفشل في هذه الحالة.

masaeedu ماذا قال zajrik . لدي مصمم ديكور يعلن عن "خدمة عبر الإنترنت" تضيف مجموعة من الخصائص إلى فئة يتم استخدامها بعد ذلك في الفصل. لا يعد التصنيف الفرعي للواجهة أو تطبيقه خيارًا بسبب فقدان البيانات الوصفية وفرض القيود (إذا كنت تسعى جاهدًا لعدم تكرار الكود).

TomMarius وجهة نظري هي أنه يتم التحقق من الكتابة بشكل صحيح. عند تطبيق وظيفة الديكور على فصل دراسي ، لا يتم تغيير الفصل بأي شكل من الأشكال. يتم إنتاج فئة جديدة من خلال بعض التحولات للفئة الأصلية ، وهذه الفئة الجديدة فقط هي المضمونة لدعم واجهة برمجة التطبيقات التي تقدمها وظيفة الديكور.

لا أعرف ما تعنيه عبارة "البيانات الوصفية المفقودة وفرض القيد" (ربما يساعد مثال ملموس) ، ولكن إذا كان فصلك يعتمد صراحة على واجهة برمجة التطبيقات التي قدمها مصمم الديكور ، فيجب أن يصنفها مباشرة عبر نمط mixin @ justinfagnani الذي تم عرضه ، أو حقنه من خلال المنشئ أو شيء من هذا القبيل. فائدة مصممي الديكور هو أنهم يسمحون بتمديد الفئات التي تم إغلاقها للتعديل من أجل الاستفادة من الكود الذي يستهلك تلك الفئات . إذا كان لديك الحرية في تحديد الفئة بنفسك ، فما عليك سوى استخدام extends .

masaeedu إذا كنت تطور نوعًا من ، دعنا نقول ، مكتبة RPC ، وتريد إجبار المستخدم على كتابة طرق غير متزامنة فقط ، فإن النهج القائم على الوراثة يفرض عليك تكرار التعليمات البرمجية (أو لم أجد الطريقة الصحيحة ، ربما - سأكون سعيدًا إذا أخبرتني إذا كنت تعرف كيف).

النهج القائم على الميراث
التعريف: export abstract class Service<T extends { [P in keyof T]: () => Promise<IResult>}> { protected someMethod(): Promise<void> { return Promise.reject(""); } }
الاستخدام: export default class MyService extends Service<MyService> { async foo() { return this.someMethod(); } }

النهج القائم على الديكور
التعريف: export function service<T>(target: { new (): T & { [P in keyof T]: () => Promise<IResult> } }) { target.someMethod = function () { return Promise.reject(""); }; return target; }
الاستخدام: <strong i="17">@service</strong> export default class { async foo() { return this.someMethod(); } }

يمكنك أن ترى بوضوح تكرار الكود في مثال النهج القائم على الوراثة. لقد حدث لي وللمستخدمين عدة مرات أنهم نسوا تغيير معلمة النوع عندما قاموا بنسخ اللصق أو بدء استخدام "أي" كمعامل للنوع وتوقفت المكتبة عن العمل معهم ؛ النهج القائم على الديكور هو أكثر ملاءمة للمطورين.

بعد ذلك ، هناك مشكلة أخرى تتعلق بالنهج القائم على الميراث: البيانات الوصفية للانعكاس مفقودة الآن ، لذلك عليك تكرار الكود أكثر لأنه يتعين عليك تقديم مصمم الديكور service على أي حال. الاستخدام الآن: <strong i="22">@service</strong> export default class MyService extends Service<MyService> { async foo() { return this.someMethod(); } } ، وهذا مجرد شيء غير ودي ، وليس مجرد إزعاج بسيط.

أنت صحيح أن التعديل يتم إجراؤه بعد تحديد الفئة ، ومع ذلك ، لا توجد طريقة لإنشاء مثيل للفئة غير المزخرفة ، لذلك لا يوجد سبب لعدم دعم طفرة الفئة بشكل صحيح بخلاف أنها تسمح أحيانًا بتشفير غير نظيف (ولكن في بعض الأحيان من أجل الصالح الأفضل). تذكر أن JavaScript لا يزال يعتمد على النماذج الأولية ، وبناء الجملة هو مجرد سكر لتغطيته. النموذج الأولي قابل للتغيير وقد يتم كتم صوته بواسطة المصمم ، ويجب كتابته بشكل صحيح.

عند تطبيق وظيفة الديكور على فصل دراسي ، لا يتم تغيير الفصل بأي شكل من الأشكال.

ليس صحيحًا ، عند تطبيق وظيفة تزيين على فصل دراسي ، يمكن تغيير الفصل بأي شكل من الأشكال. شئت ام ابيت.

TomMarius أنت تحاول استغلال الاستدلال لفرض بعض العقود ، وهو أمر لا علاقة له تمامًا بالحجة التي تدور هنا. يجب عليك فقط القيام بما يلي:

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

ليس هناك على الإطلاق أي شرط أن تكون الأجزاء الداخلية للفصل على دراية بوظيفة التزيين.

masaeedu لم تكن هذه وجهة نظري. يقدم مصمم الخدمة / فئة الخدمة بعض الخصائص الجديدة ، وهي متاحة دائمًا للفئة المراد استخدامها ، لكن نظام الكتابة لا يعكس ذلك بشكل صحيح. قلت إنني يجب أن أستخدم الميراث من أجل ذلك ، لذلك أوضحت لك لماذا لا أستطيع / لا أريد ذلك.

لقد قمت بتحرير المثال لأكون أكثر وضوحا.

masaeedu بالمناسبة ، العبارة "يعيد مصمم الديكور فئة جديدة من فئة أصلية" غير صحيحة - فكل مصمم ديكور أظهرناه هنا يعيد فئة متحولة أو مباشرة إلى الفئة الأصلية ، وليس فئة جديدة أبدًا.

TomMarius ذكر تعليقك مشكلة "إجبار المستخدم على كتابة طرق غير متزامنة فقط" ، وهي المشكلة التي حاولت معالجتها في تعليقي. إن فرض أن المستخدم قد اتبع العقد الذي تتوقعه يجب أن يتم أينما يتم تمرير رمز المستخدم مرة أخرى إلى المكتبة ، وليس له أي علاقة بالمناقشة حول ما إذا كان يجب على المصممين تغيير شكل النوع المقدم إلى الأجزاء الداخلية للفصل. يمكن حل المشكلة المتعامدة المتمثلة في توفير واجهة برمجة التطبيقات (API) لكود المستخدم باستخدام أساليب الوراثة القياسية أو التركيب.

ohjames الصف لا يتغير بمجرد تطبيق الديكور. لا تفرض JS النقاء ، لذلك من الواضح أن أي عبارة في أي مكان في التعليمات البرمجية الخاصة بك يمكنها تعديل أي شيء آخر ، ولكن هذا لا علاقة له بمناقشة الميزة هذه. حتى بعد تنفيذ الميزة ، لن يساعدك TypeScript على تتبع التغييرات الهيكلية التعسفية داخل الهيئات الوظيفية.

masaeedu أنت تختار البتات ، لكنني أتحدث عن الصورة الكبيرة. يرجى مراجعة جميع تعليقاتي في هذا الموضوع - النقطة ليست في القضايا الفردية ولكن في كل قضية تحدث في نفس الوقت. أعتقد أنني شرحت مشكلة النهج القائم على الميراث جيدًا - الكثير والكثير من تكرار الكود.

من أجل الوضوح ، لا يتعلق الأمر "برمز نظيف" بالنسبة لي. المشكلة هي مشكلة عملية. لا تحتاج إلى تغييرات كبيرة في نظام الكتابة إذا تعاملت مع @foo مثل تطبيق الوظيفة. إذا فتحت علبة الديدان لمحاولة إدخال معلومات الكتابة إلى وسيطة دالة من نوع إرجاعها ، بينما تتفاعل في نفس الوقت مع استدلال الكتابة وجميع الوحوش السحرية الأخرى الموجودة في زوايا مختلفة من نظام كتابة TypeScript ، أشعر سيصبح هذا عقبة كبيرة أمام الميزات الجديدة بنفس طريقة التحميل الزائد الآن.

TomMarius تعليقك الأول في هذا الموضوع يتعلق بالشفرة النظيفة ، وهي ليست ذات صلة. التعليق التالي يتعلق بمفهوم الخدمة عبر الإنترنت الذي قدمته كمثال رمز له. الشكوى الأساسية ، من الفقرة الأولى إلى الفقرة الرابعة ، تتعلق بكيفية تعرضها للخطأ عند استخدام MyService extends Service<MyService> . حاولت أن أبين مثالاً لكيفية التعامل مع هذا.

لقد نظرت إليه مرة أخرى ، ولا يمكنني حقًا رؤية أي شيء في هذا المثال يوضح لماذا يحتاج أعضاء الفصل المزخرف إلى أن يكونوا على دراية بمصمم الديكور. ما الذي يميز هذه الخصائص الجديدة التي توفرها للمستخدم والتي لا يمكن تحقيقها بالوراثة القياسية؟ لم أعمل مع التفكير ، لذلك كنت أخطأت في ذلك نوعًا ما ، اعتذاري.

masaeedu يمكنني تحقيق ذلك بالوراثة ، لكن الوراثة تجبرني / المستخدمين لدي على تكرار الكود بشكل كبير ، لذلك أرغب في الحصول على طريقة أخرى - وأنا أفعل ذلك ، لكن نظام الكتابة غير قادر على عكس الواقع بشكل صحيح.

النقطة هي أن النوع الصحيح من <strong i="7">@service</strong> class X { } حيث تم التصريح عن $ service على أنه <T>(target: T) => T & IService ليس X ، ولكن X & IService ؛ والمشكلة هي أن هذا صحيح في الواقع حتى داخل الفصل - على الرغم من أنه غير صحيح من الناحية اللغوية.

نقطة ألم كبيرة أخرى تسببها هذه المشكلة هي أنه عندما يكون لديك سلسلة من الديكورات لكل منها بعض القيود ، يعتقد نظام الكتابة أن الهدف هو دائمًا الفئة الأصلية ، وليس الفئة المزخرفة ، وبالتالي يكون القيد عديم الفائدة.

يمكنني تحقيق ذلك بالوراثة ، لكن الوراثة تجبرني / المستخدمين لدي على تكرار الكود بشكل كبير ، لذلك أرغب في الحصول على طريقة أخرى.

هذا هو الجزء الذي لا أفهمه. يحتاج المستخدمون لديك إلى تنفيذ IService ، وتريد التأكد من أن TheirService implements IService يطيع أيضًا بعض العقود الأخرى { [P in keyof blablablah] } ، وربما تريد أيضًا أن يحصلوا على { potato: Potato } على خدمتهم. كل هذا يسهل تحقيقه دون أن يدرك أعضاء الفصل @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 ""; }
}

في كلتا الحالتين ، لا يوجد فوز كبير في الإيجاز لا يمكن تحقيقه إلا من خلال تقديم نوع العائد serviceDecorator كنوع this إلى foo . والأهم من ذلك ، كيف تقترح الخروج باستراتيجية طباعة صوتية متساوية لهذا؟ نوع الإرجاع serviceDecorator يتم استنتاجه بناءً على نوع الفئة التي تقوم بتزيينها ، والتي بدورها تكتب الآن كنوع الإرجاع لمصمم الديكور ...

مرحبًا masaeedu ، يصبح الإيجاز ذا قيمة خاصة عندما يكون لديك أكثر من واحد.

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

ومع ذلك ، فإن هذا الحل هو بديل فقط لمصممي الفصل. بالنسبة لمصممي الطرق ، لا يوجد حاليًا حلول وكتل الكثير من التطبيقات الرائعة. الديكور في مرحلة الاقتراح 2 - https://github.com/tc39/proposal-decorators . ومع ذلك ، كان هناك الكثير من الحالات التي تم تنفيذها كثيرًا في وقت سابق. أعتقد أن المصممين على وجه الخصوص هم أحد تلك الطوب المهمة التي كانت مهمة حقًا حيث تم استخدامها بالفعل في الكثير من الأطر وتم تنفيذ إصدار بسيط جدًا بالفعل في babel / ts. إذا كان من الممكن تنفيذ هذه المشكلة ، فلن تفقد حالتها التجريبية حتى الإصدار الرسمي. ولكن هذا "تجريبي".

pietschy نعم ، سيكون من الجيد جعل بناء الجملة السكر في الصيغة @ يعمل بشكل صحيح مع فحص النوع. في الوقت الحالي ، يمكنك استخدام تكوين الوظيفة للحصول على إيجاز مماثل بشكل معقول:

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

const MyComponent = decorator(class {
});

المناقشة السابقة تدور حول ما إذا كان من الجيد القيام بنوع من الكتابة العكسية حيث يتم تقديم نوع الإرجاع لمصمم الديكور كنوع this لأعضاء الفصل.

مرحبًا masaeedu ، نعم فهمت سياق المناقشة ، ومن هنا // do stuff with undo context, component methods etc . هتافات

تحتاج حقًا إلى جعل Mixins أسهل.

لا تدعم الكتابة المطبوعة (جافا سكريبت) الوراثة المتعددة ، لذلك يجب علينا استخدام Mixins أو السمات.
والآن يضيعني الكثير من الوقت ، خاصة عندما أقوم بإعادة بناء شيء ما.
ويجب أن أقوم بنسخ ولصق واجهة مع "التنفيذ الفارغ" الخاص بها في كل مكان. (رقم 371)

-
لا أعتقد أنه يجب تقديم نوع العودة لمصمم الديكور في الفصل.
بسبب: .... emmm. لا أعرف كيف أصف ذلك ، آسف لمهاراتي الإنجليزية الضعيفة ... (🤔 هل توجد صورة بدون إطار صورة؟) هذه الوظيفة مقابل interface .

سأضيف +1 إلى هذا! أحب أن أراه قريبًا.

pietschy إذا كان الفصل يعتمد على أعضاء مضافين من قبل المصممين ، فيجب أن يوسع نتيجة المصممين ، وليس العكس. يجب عليك القيام بما يلي:

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

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

البديل هو أن يعمل نظام الكتابة في نوع من الحلقات ، حيث يتم كتابة وسيطة وظيفة الديكور حسب السياق من خلال نوع الإرجاع الخاص بها ، والذي يتم استنتاجه من وسيطته ، والتي يتم كتابتها حسب السياق بنوع الإرجاع ، وما إلى ذلك. ما زلنا بحاجة إلى اقتراح محدد لكيفية عمل ذلك ، فهو لا ينتظر التنفيذ فقط.

مرحبًا masaeedu ، أنا في حيرة من أمري لأنني سأضطر إلى تأليف مصممي الديكور وتطبيق ذلك على فئة أساسية. بقدر ما أفهمه ، تم تعديل النموذج الأولي بالفعل بحلول الوقت الذي يتم فيه تنفيذ أي كود أرض للمستخدم. على سبيل المثال

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

لذلك إذا كانت الأساليب التي حددها المصممون موجودة بالفعل ويمكن تسميتها بشكل شرعي ، فسيكون من الجيد أن تعكس الكتابة المطبوعة هذا.

الرغبة في الكتابة المطبوعة لتعكس ذلك دون فرض الحلول. إذا كان هناك آخرون
الأشياء التي يمكن لمصممي الديكور القيام بها ولا يمكن تصميمها ، فسيكون من الجيد على الأقل أن تفعل ذلك
تم دعم السيناريو في شكل أو شكل ما.

هذا الخيط مليء بالآراء حول ما يجب أن يفعله المصممون ، وكيف ينبغي استخدامها ، وكيف لا ينبغي استخدامها ، وما إلى ذلك إلى حد الغثيان .

هذا ما يفعله المصممون في الواقع :

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

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

والذي يصبح ، إذا كنت تستهدف 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);

مع __decorate يطبق كل مصمم من الأسفل إلى الأعلى ، مع كل واحد لديه خيار إرجاع فئة جديدة كاملة.

أتفهم أن الحصول على نظام الكتابة يدعم هذا الأمر ويقر به قد يكون أمرًا صعبًا للغاية ، وأنا أفهم أنه قد يستغرق وقتًا طويلاً لدعم هذا الأمر ، لكن لا يمكننا أن نتفق جميعًا على أن السلوك الحالي ، من نموذج الكود الأصلي أعلاه الذي بدأ هذا الموضوع ، هو ببساطة غير صحيح؟

بغض النظر عن رأي أي مطور في الديكور أو الخلطات أو التكوين الوظيفي وما إلى ذلك ، يبدو أن هذا خطأ واضح جدًا.


هل يمكنني الاستفسار بأدب عما إذا كان هذا يحدث في طور الإعداد لإصدار مستقبلي؟

TypeScript هو ببساطة مذهل وأنا أحبه ؛ ومع ذلك ، يبدو هذا كواحد من القطع القليلة (فقط؟) التي تم كسرها ببساطة ، وأنا أتطلع إلى رؤيتها ثابتة :)

arackaf من المفهوم جيدًا ما يفعله المصممون بالفعل ، وبافتراض أنك لا تهتم بالأنواع التي ينبعث منها TypeScript يدعم أدوات التزيين بالفعل. يدور النقاش بأكمله في هذا العدد حول كيفية تقديم الفئات المزخرفة في نظام الكتابة.

أوافق على أن الخطأ في النوع new Foo().foo هو خطأ ، ويمكن إصلاحه بسهولة عند ذلك. لا أوافق على أن الخطأ في النوع return this.foo; هو خطأ. إذا كان هناك أي شيء ، فأنت تطلب ميزة في نظام الكتابة لم يحددها أحد حتى الآن في هذه المشكلة. إذا كان لديك بعض الآليات التي يجب أن يتم من خلالها تحويل نوع this بواسطة مصمم تم تطبيقه على الفئة المحتوية ، فأنت بحاجة إلى اقتراح هذه الآلية بشكل صريح .

مثل هذه الآلية ليست تافهة كما قد تتوقع ، لأنه في جميع الحالات تقريبًا يتم استنتاج نوع الإرجاع لمصمم الديكور من نفس النوع الذي تقترح تحويله باستخدام نوع الإرجاع لمصمم الديكور. إذا أخذ مصمم الديكور addMethod فئة من النوع new () => T وأنتج new() => T & { hello(): void } ، فليس من المنطقي اقتراح أن T يجب أن يكون T & { hello(): void } . داخل جسم المصمم ، هل يصح أن أشير إلى super.hello ؟

هذا وثيق الصلة بشكل خاص لأنني لست مقيدًا بعمل return class extends ClassIWasPassed { ... } في جسم المصمم ، يمكنني فعل ما أشاء ؛ أنواع الطرح ، والأنواع المعينة ، والنقابات ، كلها لعبة عادلة. يجب أن يعمل أي نوع ناتج بشكل جيد مع حلقة الاستدلال هذه التي تقترحها. كتوضيح للمشكلة ، يرجى إخباري بما يجب أن يحدث في هذا المثال:

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

لا يمكنك التخلص من المشكلة يدويًا باعتبارها "مخادعة" ، يحتاج شخص ما إلى اقتراح كيفية عمل ذلك.

ليس خطأ - أنت تجتاز المسجل فئة تتوافق مع Greeter ؛ ومع ذلك ، يتم تغيير Foo من قبل المصمم ليكون شيئًا مختلفًا تمامًا ، وهو ما لم يعد يمتد إلى Greeter. لذلك يجب أن يكون صالحًا لمصمم المسجلات أن يعامل Foo على أنه Greeter ، ولكنه غير صالح لأي شخص آخر ، حيث تم إعادة تعيين Foo لشيء مختلف تمامًا بواسطة مصمم الديكور.

أنا متأكد من أن تنفيذ هذا سيكون صعبًا بصريًا. هل بعض المجموعات الفرعية المعقولة ممكنة ، للحالات الشائعة مثل تلك المدرجة في الجزء العلوي من هذا الموضوع ، مع حلول بديلة مثل دمج الواجهة باعتبارها احتياطيًا لحالات الحافة الصعبة مثل هذه؟

تضمين التغريدة

ومع ذلك ، يتم تغيير Foo من قبل المصمم ليكون شيئًا مختلفًا تمامًا

لا يوجد تعريف دقيق لما يمثله Foo في المقام الأول. تذكر أن نقطة الخلاف بأكملها هي ما إذا كان أعضاء Foo يجب أن يكونوا قادرين على الوصول (وبالتالي العودة كجزء من واجهة برمجة التطبيقات العامة) ، قدم مصمم الديكور أعضاء من this . ما هو Foo يتم تحديده من خلال ما يعيده المصمم ، وما يعود به المصمم يتم تحديده بما هو Foo . إنهم مقسمون بشكل لا ينفصل.

أنا متأكد من أن تنفيذ هذا سيكون صعبًا للغاية

لا يزال من السابق لأوانه الحديث عن تعقيد التنفيذ ، في حين أننا لا نمتلك اقتراحًا قويًا حول كيفية عمل الميزة. نعتذر عن العزف على هذا ، لكننا نحتاج حقًا إلى شخص ما ليقترح آلية محددة لـ "ما يحدث لـ this عندما أطبق مثل هذا التسلسل من الديكور على مثل هذه الفئة". يمكننا بعد ذلك إدخال مفاهيم TypeScript المختلفة في أجزاء مختلفة ومعرفة ما إذا كان ما يأتي منطقيًا. عندها فقط يكون من المنطقي مناقشة تعقيد التنفيذ.

هل ناتج الديكور الحالي الذي قمت بلصقه أعلاه غير متوافق مع المواصفات؟ افترضت أنه من ما رأيته.

بافتراض ذلك ، فإن Foo له معنى دقيق إلى حد ما في كل خطوة على الطريق. أتوقع أن يسمح لي TS باستخدام this (أساليب Inside of Foo وفي حالات Foo) بناءً على ما يعيده مصمم الديكور الأخير ، حيث يجبرني TS على إضافة التعليقات التوضيحية على طول الطريق حسب الحاجة إذا كان المصمم الوظائف غير قابلة للتحليل بشكل كافٍ.

arackaf لا توجد "خطوات على الطريق" ؛ نحن مهتمون فقط بالنوع النهائي this لمقتطف شفرة محدد غير قابل للتغيير. تحتاج إلى وصف ، على الأقل على مستوى عالٍ ، ما تتوقعه من نوع this يجب أن يكون لأعضاء فئة X مزين بوظائف الديكور f1...fn ، من حيث التوقيعات من النوع X و f1...fn . يمكنك إجراء العديد من الخطوات في العملية كما تريد. حتى الآن ، لم يقم أحد بذلك. لقد كنت أظن أن الناس يقصدون أن نوع الإرجاع لمصمم الديكور يجب أن يتم تقديمه كنوع this ، لكن لكل ما أعرفه يمكن أن أكون بعيدًا تمامًا عن العلامة.

إذا كان اقتراحك هو جعل الأنواع تعكس آليًا ما يحدث للقيم في المخرجات المترجمة ، فسينتهي بك الأمر إلى ما أقترحه بدلاً من ما تقترحه: على سبيل المثال ، new Foo().hello() جيد ، لكن this.hello() ليس كذلك. في أي وقت من الأوقات في هذا المثال ، لا تكسب الفئة الأصلية التي تزينها طريقة hello . فقط نتيجة __decorate([addMethod], Foo) (التي تم تعيينها بعد ذلك إلى Foo ) لها طريقة hello .

لقد كنت أظن أن الناس يقصدون أنه يجب تقديم نوع العودة لمصمم الديكور كنوع من هذا

أوه ، آسف ، نعم ، هذا صحيح. هذا بالضبط. نقطة. لأن هذا ما يفعله المصممون. إذا أعاد مصمم الديكور الأخير فئة جديدة تمامًا ، فهذا هو Foo.

بعبارات أخرى:

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

Foo هو أي شيء يقوله في العالم c . إذا كانت c دالة تُرجع any إذن ، فأنا لا أعرف - ربما مجرد الرجوع إلى Foo الأصلي؟ يبدو أن هذا نهج معقول ومتوافق مع الإصدارات السابقة.

ولكن إذا c نوعًا جديدًا X ، فأنا أتوقع بالتأكيد Foo أن يحترم ذلك.

هل فاتني شيء؟


توضيح المزيد ، إذا

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 

هل فاتني شيء؟

arackaf نعم ، ما ينقص هذا النهج الساذج هو أن المصمم له الحرية في إرجاع الأنواع العشوائية ، مع عدم وجود قيود على أن تكون النتيجة متوافقة مع نوع Foo .

هناك أي عدد من السخافات التي يمكن أن تنتجها بهذا. لنفترض أنني أوقفت اعتراضي على دائرية كتابة this كنتيجة c ، والذي يتم تحديده بواسطة نوع this ، والذي يتم تحديده بواسطة نتيجة c ، إلخ. هذا ما زال لا يعمل. إليك مثال آخر لا معنى له:

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

في هذه الحالة ، إما أن تكون مجبرًا على إنتاج خطأ لرمز حميد تمامًا ، أو تعديل الاقتراح القائل بأن this مطابق تمامًا لنوع إرجاع مصمم الديكور.

لست متأكدًا من أنني أرى المشكلة. اختار @logger إرجاع فئة جديدة تمامًا ، بدون أي طريقة foo ، وبطريقة hello() جديدة تمامًا ، والتي تحدث للإشارة إلى الأصل ، الذي لا يمكن الوصول إليه الآن Foo .

new Foo().foo()

لم يعد صالحا؛ سينتج خطأ وقت التشغيل. أنا فقط أقول أنه يجب أن ينتج عنه خطأ في وقت الترجمة.

ومع ذلك ، إذا كان التحليل الثابت لكل ذلك صعبًا للغاية ، فسيكون من المعقول تمامًا إجبارنا على إضافة التعليقات التوضيحية من النوع الصريح إلى logger للإشارة بالضبط إلى ما يتم إرجاعه. وإذا لم يكن هذا النوع من التعليقات التوضيحية موجودًا ، فسأقول فقط ارجع إلى افتراض إرجاع Foo مرة أخرى. يجب أن يبقيها متوافقة مع الإصدارات السابقة.

arackaf لا توجد مشكلة في الكود من حيث الكتابة أو تقييم وقت التشغيل. يمكنني الاتصال بـ new Foo().hello() ، والذي سيتصل داخليًا بالفئة المزخرفة hello ، والتي ستطلق على الفصل المزين bar . ليس من الخطأ بالنسبة لي استدعاء bar داخل الفصل الدراسي الأصلي.

يمكنك تجربتها بنفسك من خلال تشغيل هذا المثال الكامل في الملعب:

// Code from previous snippet...

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

بالتأكيد - لكنني قلت أنه كان من الخطأ الاستدعاء

new Foo().foo()

لا توجد مشكلة في الكود من حيث الكتابة أو تقييم وقت التشغيل. يمكنني استدعاء Foo الجديد (). hello () ، والذي سوف يستدعي داخليًا كلمة hello الخاصة بالفئة المزخرفة ، والتي ستستدعي شريط الفصل المزين

ولكن ينبغي أن يكون من الخطأ القول

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

نظرًا لأن طريقة Foo's hello تُرجع الآن باطلة ، لكل فئة يقوم المسجل بإرجاعها.

بالتأكيد - لكني قلت إن استدعاء new Foo().foo() خطأ

arackaf لكن هذا لا يهم. لم أستدعي new Foo().foo() . قمت باستدعاء this.foo() ، وتلقيت خطأ في النوع ، على الرغم من أن الكود الخاص بي يعمل بشكل جيد في وقت التشغيل.

ولكن يجب أن يكون من الخطأ قول let s : string = new Foo().hello()

مرة أخرى ، هذا غير ذي صلة. أنا لا أقول أن النوع الأخير من Foo.prototype.hello يجب أن يكون () => string (أوافق أنه يجب أن يكون () => void ). أنا أشتكي من الاستدعاء الصحيح this.bar() خطأ ، لأنك قمت بزرع نوع جراحيًا حيث يكون من غير المنطقي زرعه.

هناك نوعان من Foo's هنا. عندما تقول

class Foo { 
}

Foo هو ارتباط غير قابل للتغيير داخل الفئة ، ورابط قابل للتغيير خارج الفصل. لذلك يعمل هذا بشكل مثالي كما يمكنك التحقق منه في ملف jsbin

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

let F = Foo;

Foo = null;

new F().hello();

المثال الخاص بك أعلاه يفعل أشياء مماثلة ؛ إنه يلتقط إشارة إلى الفئة الأصلية قبل أن يتم تغيير هذا الربط الخارجي. ما زلت غير متأكد مما تقود فيه.

this.foo(); صالح تمامًا ، ولا أتوقع خطأ في النوع (كما أنني لن ألوم موظفي TS إذا كنت بحاجة إلى أي مرجع ، لأنني متأكد من أن تتبع ذلك سيكون صعبًا)

this.foo(); صالح تمامًا ، ولا أتوقع خطأ في النوع

جيد ، لذلك نحن نتفق ، ولكن هذا يعني أنه يتعين عليك الآن تأهيل أو رفض الاقتراح بأن يكون this هو نوع المثيل لكل ما يقوم المصمم بإرجاعه. إذا كنت تعتقد أنه لا ينبغي أن يكون خطأ نوع ، فماذا يجب أن يكون this بدلاً من { hello(): void } في المثال الخاص بي؟

يعتمد this على ما تم تكوينه.

<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
}

هل يمكننا من فضلك التمسك بمثال واحد ملموس؟ هذا من شأنه أن يجعل الأمور أسهل بالنسبة لي للفهم. في المقتطف التالي:

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(); } /// <------
}

ما هو نوع this ؟ إذا كان { hello(): void } ، فسأظهر لي خطأ في النوع ، لأن foo ليس عضوًا في { hello(): void } . إذا لم يكن { hello(): void } ، فإن this ليس مجرد نوع مثيل لنوع إرجاع مصمم الديكور ، وتحتاج إلى شرح أي منطق بديل تستخدمه للوصول إلى نوع this .

تحرير: نسيت أن تضيف الزخرفة على Foo . مثبت.

this ، حيث توجد الأسهم ، هو بالطبع مثيل لـ Foo الأصلي. لا يوجد خطأ في النوع.

آه - أرى وجهة نظرك الآن ؛ لكني ما زلت لا أرى أين تكمن المشكلة. this.foo() داخل Foo الأصلي ليس خطأ نوع - وهذا صالح للفئة (التي لا يمكن الوصول إليها الآن) التي كانت مرتبطة بالمعرف Foo .

إنها تافهة خاصة وممتعة ، لكنني لا أرى سببًا يمنع ذلك TS من التعامل مع الديكورات الصفية المتغيرة.

arackaf أنت لا تجيب على السؤال. ما هو بالتحديد نوع this ؟ لا يمكنك الإجابة بشكل دائري إلى ما لا نهاية " this هو Foo و Foo هو this ". من هم الأعضاء الذين يمتلكون this ؟ إذا كان به أي أعضاء إلى جانب hello(): void ، فما المنطق المستخدم لتحديدهم؟

عندما تقول " this.foo() داخل Foo الأصلي ليس خطأ من النوع" ، ما زلت بحاجة للإجابة على السؤال: ما هو النوع الهيكلي لـ this بحيث لا يكون خطأ في النوع تفعل this.foo() ؟

كما أن الفصل الأصلي ليس "بعيد المنال". يتم ممارسة كل وظيفة محددة في مقتطف الشفرة هذا بنشاط في وقت التشغيل ، وكلها تعمل دون عوائق. يرجى تشغيل رابط الملعب الذي قدمته وإلقاء نظرة على وحدة التحكم. يقوم المصمم بإرجاع فئة جديدة حيث تقوم طريقة hello بتفويض طريقة hello للطبقة المزخرفة ، والتي بدورها تفوض إلى طريقة foo للطبقة المزخرفة.

إنها تافهة خاصة وممتعة ، لكنني لا أرى سببًا يمنع ذلك TS من التعامل مع الديكورات الصفية المتغيرة.

لا يوجد "تافه" في نظام الكتابة. لن تحصل على TSC-1234 "صبي شقي ، لا يمكنك فعل ذلك" لأن حالة الاستخدام مناسبة جدًا. إذا تسببت إحدى الميزات في تعطل الكود العادي تمامًا بطرق مدهشة ، فيجب إعادة التفكير في الميزة.

لن تحصل على TSC-1234 "صبي شقي ، لا يمكنك فعل ذلك" لأن حالة الاستخدام مناسبة جدًا.

هذا بالضبط ما أحصل عليه عندما أحاول استخدام طريقة تمت إضافتها إلى تعريف فئة بواسطة مصمم ديكور. لا بد لي حاليًا من التغلب عليها إما بإضافة تعريف للفصل ، أو استخدام دمج الواجهة ، أو الإرسال كـ any إلخ.

لقد أجبت على كل سؤال حول ماهية this وأين.

الحقيقة البسيطة للأمر هي أن معنى this يتغير بناءً على مكانك.

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"

عندما تقول new Class() - يشير Class إلى Foo الأصلي ، ومن منظور TypeScript سيكون بإمكانه الوصول إلى hello(): string لأن هذا هو ما تمت كتابة الفصل به (يمتد Greeter). في وقت التشغيل ، ستقوم بإنشاء مثيل لـ Foo الأصلي.

يحدث new LoggableFoo().hello() لاستدعاء طريقة باطلة تحدث لاستدعاء طريقة لا يمكن الوصول إليها بطريقة أخرى ، تتم كتابتها من خلال Greeter.

إذا كنت قد فعلت

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

إذن Foo هو الآن فئة بها hello (): void والجديدة Foo (). foo () يجب أن يكون خطأ من النوع.

ومرة أخرى ، hello() { return this.foo(); } ليس خطأ من النوع - لماذا يكون؟ فقط لأن تعريف الفئة هذا لم يعد قابلاً للوصول لا يجعل هذه الطريقة غير صالحة بطريقة ما.

لا أتوقع أن تحصل TypeScript على أي من حالات الحافة هذه بشكل مثالي ؛ سيكون من المفهوم تمامًا أن تضطر إلى إضافة نوع التعليقات التوضيحية هنا وهناك ، كما هو الحال دائمًا. ولكن لا يُظهر أي من هذه الأمثلة سبب عدم قدرة @logger على تغيير ما يرتبط به Foo بشكل أساسي.

إذا كانت logger دالة تقوم بإرجاع فئة جديدة ، فهذا ما يشير إليه Foo الآن.

لقد أجبت على كل سؤال حول ماهية هذا وأين.
الحقيقة البسيطة للأمر هي أن معنى this يتغير بناءً على مكانك.

هذا حقا محبط حسنًا ، إنه يتغير ، إنه Foo ، إنه ربط ثابت ، إلخ. إلخ. ما هو نوع التوقيع؟ من هم الأعضاء الذين يمتلكون this ؟ أنت تتحدث عن كل شيء تحت الشمس ، عندما يكون كل ما أحتاجه منك هو توقيع بسيط من النوع لما هو this داخل hello .

يحدث new LoggableFoo().hello() لاستدعاء طريقة باطلة تحدث لاستدعاء طريقة لا يمكن الوصول إليها بطريقة أخرى ، تتم كتابتها من خلال Greeter.

هذا ليس هو نفسه لا يمكن الوصول إليه. كل طريقة يمكن الوصول إليها "لا يمكن الوصول إليها بأي طريقة أخرى" عندما تحذف المسارات التي يمكن الوصول إليها من خلالها.

إذا كنت قد فعلت:

لكن هذا ما فعلته حرفياً . هذا هو بالضبط مقتطف الشفرة الخاص بي ، الذي تم لصقه مرة أخرى ، مقدمًا بشرح للمثال الذي أنشأته لك. حتى أنني قمت بتشغيله عبر مدقق فرق للتأكد من أنني لم أتناول حبوب مجنونة ، والفرق الوحيد هو التعليق الذي أزلته.

ومرة أخرى ، hello() { return this.foo(); } ليس خطأ من النوع - لماذا يكون؟

لأنك (والآخرين هنا) تريد أن يكون نوع this هو نوع الإرجاع المُنشأ لمصمم الديكور ، وهو { hello(): void } (لاحظ عدم وجود عضو foo ). إذا كنت تريد أن يتمكن أعضاء Foo من رؤية this كنوع إرجاع لمصمم الديكور ، فإن نوع this داخل hello سيكون { hello(): void } . إذا كان { hello(): void } ، يظهر لي خطأ في النوع. إذا تلقيت خطأ في النوع ، فأنا حزين ، لأن الكود الخاص بي يعمل بشكل جيد.

إذا قلت أنه ليس خطأ في النوع ، فأنت تتخلى عن مخططك الخاص لتزويد نوع this عبر نوع الإرجاع لمصمم الديكور. نوع this هو ثم { hello(): string; bar(): string } ، بغض النظر عن ما يقوم المصمم بإرجاعه. قد يكون لديك مخطط بديل لإنتاج نوع this الذي يتجنب هذه المشكلة ، لكنك تحتاج إلى تحديد ما هو عليه.

يبدو أنك لا تفهم أنه بعد تشغيل أدوات الديكور ، يمكن أن يشير Foo إلى شيء منفصل تمامًا عما تم تعريفه في الأصل عليه.

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

أظن أنك وجدت بعض الغرابة في this لكونه شيئًا مختلفًا داخل Foo أعلاه ، مما هو عليه في الحالات التي تم إنشاؤها لاحقًا مما هو Foo في النهاية (بعد تشغيل الديكور)؟

لست متأكدًا حقًا مما سأقوله لك ؛ هذه هي الطريقة التي يعمل بها المصممون. حجتي الوحيدة هي أن TypeScript يجب أن تتطابق بشكل وثيق مع ما يحدث.

بعبارة أخرى ، ستكون توقيعات الكتابة داخل Foo (الأصلية) مختلفة عما هو / ينتج Foo بمجرد تشغيل المصممين.

لاستعارة تشبيه من لغة أخرى ، داخل Foo المزخرف ، قد يشير هذا إلى ما يعادل نوع C # مجهول - نوع حقيقي تمامًا ، وهذا صحيح بخلاف ذلك ، فقط لا يمكن الرجوع إليه مباشرة. قد يبدو غريبًا الحصول على الأخطاء وعدم وجود أخطاء موصوفة أعلاه ، ولكن مرة أخرى ، هذه هي الطريقة التي يعمل بها. يمنحنا المصممون قوة هائلة للقيام بأشياء غريبة مثل هذه.

أظن أنك تجد بعض الغرابة في كون هذا شيئًا مختلفًا داخل Foo أعلاه ، مما هو عليه في الحالات التي تم إنشاؤها لاحقًا مما هو Foo في النهاية (بعد تشغيل الديكور)؟

لا. لا أجد أي غرابة في ذلك ، لأنه بالضبط ما كنت أقترحه قبل 200 تعليق. هل قرأت حتى أيًا من المناقشة السابقة؟

المقتطف الذي نشرته غير مثير للجدل تمامًا. الأشخاص الذين كنت أختلف معهم والذين قفزت لمساعدتهم يريدون بالإضافة إلى ذلك ما يلي:

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

لا أوافق على أنه من الممكن القيام بذلك بشكل سليم ، وكنت أحاول بشدة معرفة كيفية عمل مثل هذا الاقتراح. على الرغم من بذل قصارى جهدي ، لا يمكنني استخراج قائمة شاملة بالأعضاء المتوقع أن يمتلكهم this ، أو كيف سيتم إنشاء هذه القائمة بموجب الاقتراح.

بعبارة أخرى ، ستكون توقيعات الكتابة داخل Foo (الأصلية) مختلفة عما هو / ينتج Foo بمجرد تشغيل المصممين.

فلماذا حتى تتجادل معي؟ قلت: "أوافق على أن Foo (). foo الجديد هو خطأ في النوع ، وهو خطأ يمكن إصلاحه بسهولة عند ذلك. لا أوافق على إرجاع this.foo ؛ فكونه خطأ من النوع هو خطأ". بشكل مشابه ، في مثالك ، أوافق على أن الخطأ في النوع new Foo().x() هو خطأ ، لكن الخطأ في النوع this.x() ليس كذلك.

ترى كيف يوجد تعليقان في المقتطف في أعلى هذه الصفحة؟

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

^ هذا هو الذي أجده مشكلة. إما أنك توافق على أنه لا ينبغي تقديم نوع إرجاع مصمم الديكور على this ، وفقط على new Foo() ، وفي هذه الحالة كلانا نتجادل حول لا شيء. أو أنك لا توافق على هذه الميزة وتريدها أيضًا ، وفي هذه الحالة يكون المقتطف في تعليقك السابق غير ذي صلة.

أخيرا فهمت وجهة نظرك. كان من الصعب للغاية بالنسبة لي الحصول على هذا من كود Greeter الخاص بك ، لكنني أتتبع الآن ؛ شكرا لك لأنك مريض جدا.

أود أن أقول إن الحل المعقول الوحيد هو أن يدعم Foo ( داخل Foo) اتحاد النوع - derp الذي قصدته التقاطع - لـ Foo الأصلي ، وأيًا كان ما يعود به آخر مصمم. يجب عليك دعم Foo الأصلي للحصول على أمثلة مجنونة مثل Greeter الخاص بك ، وتحتاج بالتأكيد إلى دعم كل ما يعود آخر مصمم الديكور لأن هذا هو الهدف الكامل من استخدام الزخارف (وفقًا للتعليقات العديدة أعلاه).

حسنًا ، من أحدث مثال لي ، داخل Foo x ، y ، z ، a ، b ، c ستعمل جميعها. إذا كان هناك إصداران من a ، فقم بدعم كلا الإصدارين.

arackaf np ، شكرًا على سعة صدرك أيضًا. لم تكن الأمثلة الخاصة بي هي الأكثر وضوحًا لأنني أنشر فقط كل ما يمكنني طهيه في الملعب والذي يوضح كيفية تعطله. يصعب علي التفكير في الأمر بشكل منهجي.

أود أن أقول إن الحل المعقول الوحيد هو أن تدعم Foo (داخل Foo) اتحاد النوع الخاص بـ Foo الأصلي ، وأيًا كان آخر مصمم ديكور.

حسنًا ، رائع ، لذا فإننا ندخل في تفاصيله الآن. صححني إذا كنت أفهم هذا بشكل خاطئ ، ولكن عندما تقول "union" ، فأنت تعني أنه يجب أن يحتوي على أعضاء من النوعين ، أي يجب أن يكون A & B . لذلك نريد أن يكون this typeof(new OriginalClass()) & typeof(new (decorators(OriginalClass))) ، حيث decorators هو توقيع النوع المكون لجميع المصممين. في اللغة الإنجليزية البسيطة ، نريد أن يكون this تقاطع النوع الذي تم إنشاء مثيل له من "الفئة الأصلية" والنوع الذي تم تكوينه من "الفئة الأصلية" الذي يمر عبر جميع المصممين.

هناك مشكلتان مع هذا. أحدها هو أنه في حالات مثل مثالي ، هذا يسمح لك فقط بالوصول إلى أعضاء غير موجودين. يمكنني إضافة مجموعة من الأعضاء في مصمم الديكور ، ولكن إذا حاولت الوصول إليهم في صفي باستخدام this.newMethod() ، فسيكون ذلك مجرد تقيؤ في وقت التشغيل. تتم إضافة newMethod فقط إلى الفصل الذي يتم إرجاعه من وظيفة التزيين ، ولا يمكن لأعضاء الفصل الأصلي الوصول إليه (إلا إذا صادفت استخدام النمط return class extends OriginalClass { newMethod() { } } ).

المشكلة الأخرى هي أن "الفئة الأصلية" ليست مفهومًا محددًا جيدًا. إذا كان بإمكاني الوصول إلى الأعضاء المزينين من this ، فيمكنني أيضًا استخدامها كجزء من بيانات الإرجاع ، وبالتالي قد يكونوا جزءًا من واجهة برمجة التطبيقات العامة لـ "الفئة الأصلية". أنا من النوع الذي ألوح باليد هنا ، وأنا منهك جدًا للتفكير في أمثلة ملموسة ، لكنني أعتقد أنه إذا فكرنا مليًا في الأمر ، يمكننا التوصل إلى أمثلة لا معنى لها. ربما يمكنك التغلب على هذا من خلال إيجاد طريقة ما لفصل الأعضاء الذين لا يعيدون الأشياء التي وصلوا إليها من this أو على الأقل لم يتم استنتاج نوع الإرجاع نتيجة إرجاع this.something() .

masaeedu نعم ، لقد صححت نفسي على شيء الاتحاد / التقاطع قبل ردك. إنه أمر غير بديهي بالنسبة لشخص جديد في TS.

فهمت على الباقي. بصراحة ، لن يقوم المصممون عادةً بإرجاع نوع مختلف تمامًا ، فعادة ما يقومون فقط بزيادة النوع الذي تم تمريره ، لذلك سيعمل شيء التقاطع بأمان معظم الوقت.

أود أن أقول إن أخطاء وقت التشغيل التي تتحدث عنها ستكون نادرة ، ونتيجة لبعض قرارات التطوير السيئة المتعمدة. لست متأكدًا من أنك يجب أن تهتم بهذا الأمر حقًا ، ولكن إذا كانت هذه مشكلة حقًا ، فسأقول فقط باستخدام آخر مصمم تم إرجاعه سيكون مكانًا ثانيًا لائقًا (لذلك ، نعم ، يمكن للفصل أن يرى خطأ من النوع بواسطة محاولة استخدام طريقة تحددها بنفسها - ليست مثالية ، لكنها لا تزال ثمنًا يستحق دفعه مقابل أعمال الديكور).

لكن في الحقيقة ، أعتقد أن أخطاء وقت التشغيل التي تفكر فيها لا تستحق المنع ، وبالتأكيد على حساب المصممين الذين يعملون بشكل صحيح. علاوة على ذلك ، من السهل جدًا إنتاج أخطاء وقت تشغيل في TS إذا كنت مهملاً أو سخيفًا.

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

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

بخصوص اعتراضك الثاني

يمكنني أيضًا استخدامها كجزء من بيانات الإرجاع ، وبالتالي قد تكون جزءًا من واجهة برمجة التطبيقات العامة لـ "الفئة الأصلية"

أخشى أنني لا أرى أي مشاكل في ذلك. أريد أي شيء يضاف إلى الفصل عن طريق مصمم الديكور لأكون مطلقًا مواطنًا من الدرجة الأولى. لدي فضول لمعرفة المشاكل المحتملة هناك.

أعتقد أن أحد الخيارات الجيدة الأخرى هو تنفيذ هذا جزئيًا فقط.

حاليًا ، يتم دائمًا كتابة الفصول الدراسية كما هي محددة. لذلك داخل Foo ، يعتمد هذا على ومع ذلك يتم تعريف foo ، بغض النظر عن المصممين. سيكون تحسينًا هائلاً وهائل لتوسيع ذلك بالنسبة لمجموعة فرعية مفيدة من حالات استخدام المصمم (أي الحالات الأكثر شيوعًا)

ماذا لو سمحت بتمديد الفصل (من منظور this داخل الفصل) إذا وفقط إذا قام المصمم بإرجاع شيء يمتد إلى الأصل ، على سبيل المثال

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

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

اجعلها تعمل فقط ، وقدم دعمًا من الدرجة الأولى إلى blah وأي شيء آخر أضافه مصمم الديكور. ولحالات الاستخدام التي تقوم بأشياء مجنونة مثل إعادة فصل جديد بالكامل (مثل Greeter الخاص بك) ، فقط استمر في السلوك الحالي وتجاهل ما يفعله المصمم.


راجع للشغل ، بغض النظر عن ما تختاره ، كيف سأضع تعليقًا توضيحيًا على ذلك؟ هل يمكن التعليق على هذا حاليا؟ حاولت

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

بالإضافة إلى العديد من الأشكال المختلفة حول هذا الموضوع ، ولكن لم يكن لدى TypeScript أيًا منها :)

arackaf الديكور هو مجرد وظيفة. في الحالة العامة ، ستحصل عليه من ملف .d.ts في مكان ما ، وليس لديك أي فكرة عن كيفية تنفيذه. أنت لا تعرف ما إذا كان التطبيق يعيد فصلًا جديدًا بالكامل ، أو إضافة / طرح / استبدال الأعضاء في النموذج الأولي للفصل الأصلي وإعادته ، أو تمديد الفصل الأصلي. كل ما هو متاح لديك هو نوع الإرجاع الهيكلي للوظيفة.

إذا كنت ترغب في ربط المصممين بطريقة ما بالميراث الطبقي ، فأنت بحاجة إلى اقتراح مفهوم لغة منفصل لـ JS أولاً. الطريقة التي يعمل بها المصممون اليوم لا تبرر تغيير this في الحالة العامة. على سبيل المثال ، أنا شخصياً أفضل التركيب على الميراث ، وسأفعل دائمًا :

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

هذه ليست تجربة أكاديمية مجنونة ، إنها نهج معياري لعمل الخلطات ، وتتعارض مع المثال الذي قدمته لك. في الواقع ، أي شيء تقريبًا باستثناء return class extends Class ينفصل عن المثال الذي أعطيته لك ، وفي كثير من الحالات ، ستتعادل الأشياء مع return class extends Class .

سيتعين عليك القفز عبر جميع أنواع الحلقات والتواءات لإنجاح هذا الأمر ، وسيحاربك نظام الكتابة في كل خطوة على الطريق ، لأن ما تفعله غير منطقي في الحالة العامة. الأهم من ذلك ، بمجرد تنفيذه ، سيتعين على الجميع المناورة بهلوانية حول هذه الزاوية غير المنطقية من نظام الكتابة كلما حاولوا تنفيذ مفهوم آخر معقد (لكن سليم).

لمجرد أن لديك حالة استخدام واحدة تشعر بأنها مهمة (وقد حاولت عدة مرات في هذا الموضوع إظهار طرق بديلة للتعبير عما تريده بشكل سليم في نظام الكتابة الحالي) ، فهذا لا يعني أن الشيء الوحيد الصحيح ما عليك القيام به هو المضي قدمًا في النهج الذي اقترحته بأي ثمن. يمكنك دمج واجهات عشوائية في فصلك الدراسي ، بما في ذلك أنواع إرجاع وظائف الديكور ، لذلك ليس من المستحيل بالنسبة لك الوصول إلى أي مكان إذا كنت تصر على استخدام أدوات الديكور بالطريقة التي تستخدمها.

arackaf هذا:

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

يجب أن تعمل بشكل جيد في عبارة التمديد:

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

مع دلالات أسهل بكثير ومحددة بالفعل في نظام الكتابة. هذا يعمل بالفعل. ما هي المشكلة التي تحاول حلها عن طريق الفئات الفرعية للفئة المُعلنة ، بدلاً من إنشاء فئة فائقة لها؟

تضمين التغريدة

الطريقة التي يعمل بها المصممون اليوم لا تبرر تغيير هذا في الحالة العامة.

الاستخدام الأساسي لمصممي (الفئة) هو بالتأكيد ، بشكل إيجابي ، تغيير قيمة this داخل الفصل ، بطريقة أو بأخرى. يأخذ كل من Redux @connect و MobX @observer فصلًا دراسيًا ، ويخرجون إصدارًا جديدًا من الفصل الأصلي ، مع ميزات إضافية. في تلك الحالات بالذات ، لا أعتقد أن الهيكل الفعلي لـ this يتغير (فقط هيكل this.props ) ، لكن هذا غير جوهري.

من الشائع جدًا (كما ترون من التعليقات أعلاه) استخدام حالة استخدام الزخرفة لتغيير فئة بطريقة ما). للأفضل أو للأسوأ يميل الناس إلى كره البدائل النحوية فقط

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

في مقابل

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

الآن ، ما إذا كان مصمم الديكور

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

أو

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

لا ينبغي أن يهم. بطريقة أو بأخرى ، حالة الاستخدام التي يطالب بها الكثيرون حقًا ، هي أن تكون قادرًا على إخبار TypeScript ، بأي تعليقات توضيحية ضرورية ، بغض النظر عن مدى إزعاجنا ، أن function c يأخذ فصلًا C in ، وإرجاع فئة بها البنية C & {blah(): void} .

هذا ما يستخدمه الكثير منا بنشاط في مجال الديكور لهذا اليوم. نريد حقًا دمج مجموعة فرعية مفيدة من هذا السلوك في نظام الكتابة.

من السهل إثبات أن المصممين يمكنهم القيام بأشياء غريبة يصعب على نظام الكتابة تتبعها. بخير! ولكن يجب أن تكون هناك طريقة ما لتوضيح ذلك

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

صالح. لا أعرف ما إذا كان سيتطلب بنية جديدة للتعليقات التوضيحية من النوع c ، أو إذا كان من الممكن الضغط على بناء جملة التعليقات التوضيحية من النوع الحالي في الخدمة على c لإنجاز ذلك ، ولكن فقط إصلاح الاستخدام العام الحالة (وترك الحواف كما هي ، مع عدم حدوث أي تأثير على الفئة الأصلية) سيكون بمثابة نعمة كبيرة لمستخدمي TS.

تضمين التغريدة

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

ينتج عنه

النوع 'typeof (Anonymous class)' غير قابل للتخصيص للكتابة 'T & (new () => {blah (): void؛})'.
النوع "typeof (فئة Anonymous)" غير قابل للتخصيص لكتابة "T".

في ملعب TS. لست متأكدًا مما إذا كان لدي بعض الخيارات المعينة بشكل خاطئ.

ما هي المشكلة التي تحاول حلها عن طريق الفئات الفرعية للفئة المُعلنة ، بدلاً من إنشاء فئة فائقة لها؟

أنا أحاول فقط استخدام الديكور. لحسن الحظ كنت أستخدمها في JS لأكثر من عام - أنا فقط حريص على TS للحاق بالركب. وأنا بالتأكيد لست متزوجًا من فئة فرعية. في بعض الأحيان يغير المصمم النموذج الأولي للفئة الأصلية ، لإضافة خصائص أو طرق. في كلتا الحالتين ، الهدف هو إخبار TypeScript بذلك

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

سينتج عن فصل دراسي به أعضاء جدد ، تم إنشاؤه بواسطة c .

arackaf نحن نخرج من المزامنة مرة أخرى. النقاش ليس حول هذا:

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

مقابل هذا:

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

أتوقع أن تكون قادرًا على استخدام السابق ولا يزال لديك كتابة مناسبة لـ new Foo() . النقاش حول هذا:

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

مقابل هذا:

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

هذا الأخير سيعمل دون كسر نظام الكتابة. السابق يمثل إشكالية من عدة نواحٍ أوضحتها بالفعل.

masaeedu ألا توجد حالات استخدام مقيدة حقًا يمكن فيها تشغيل السابق؟

على وجه الدقة: هل لا توجد تعليقات توضيحية يمكن أن يخبرنا TypeScript أن نضيفها إلى a ، b ، و c من المثال أعلاه لكي يتعامل نظام الكتابة مع السابق لا يمكن تمييزه عن الأخير؟

سيكون هذا تقدمًا قاتلًا لـ TypeScript.

arackaf آسف ، عليك أن تجعل معلمة النوع تشير إلى نوع المُنشئ ، وليس نوع المثيل:

هذا يعمل:

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

أنا أحاول فقط استخدام الديكور.

هذه ليست مشكلة تحاول حلها حقًا ، وهي عبارة عن حشو. إنه مثل سؤال: "ما الذي تحاول فعله بهذه المطرقة؟" ج: "فقط استخدم المطرقة".

هل هناك حالة فعلية تفكر فيها حيث لا يعمل إنشاء فئة فائقة جديدة بالامتداد المقصود؟ نظرًا لأن TypeScript يدعم ذلك الآن ، وكما قلت ، فمن الأسهل بكثير التفكير في نوع الفصل من داخل الفصل في هذه الحالة.

نظرت إلى التعليق التوضيحي لـ Mobx @observer ويبدو أنه لا يغير شكل الفصل (ويمكن بسهولة أن يكون مصممًا للطريقة بدلاً من مصمم الديكور لأنه يلتف فقط render() ).

@connect هو في الواقع مثال رائع على تعقيدات كتابة الزخرفة بشكل صحيح ، لأن @connect يبدو أنه لا يُرجع حتى فئة فرعية من الطبقة المزخرفة ، ولكن فئة جديدة تمامًا تغلف الطبقة المزخرفة ، ومع ذلك يتم نسخ الإحصائيات ، وبالتالي فإن النتيجة ترمي واجهة جانب المثيل بعيدًا وتحافظ على الجانب الثابت الذي لا يتحقق TS حتى في معظم الحالات. يبدو أنه لا ينبغي أن يكون @connect مصممًا على الإطلاق ويجب استخدام الاتصال فقط كمخطط HOC ، لأنه كمصمم ديكور ، إنه أمر محير للغاية.

لحسن الحظ كنت أستخدمها في JS لأكثر من عام

حسنًا ... أنت لم تفعل ذلك حقًا. JS ليس لديه ديكور. من المحتمل أنك كنت تستخدم اقتراحًا سابقًا معينًا غير صالح ، والذي تم تنفيذه بشكل مختلف قليلاً في Babel و TypeScript. في أماكن أخرى ، أدافع عن مصممي الديكور لمواصلة عملية التوحيد القياسي ، لكن التقدم تباطأ ولا أعتقد أنه مؤكد حتى الآن. يجادل الكثير بأنه لا ينبغي إضافتها ، أو أن تكون مجرد تعليقات توضيحية ، لذلك قد تظل الدلالات في الهواء.

إحدى الشكاوى ضد مصممي الديكور هي بالضبط أنهم يستطيعون تغيير شكل الفصل ، مما يجعل من الصعب التفكير بشكل ثابت ، وقد تم الحديث عن بعض المقترحات على الأقل للحد من قدرة المصممين على القيام بذلك ، على الرغم من أنني أعتقد أن ذلك كان قبل أحدث اقتراح حصل على لغة محددة. أنا شخصياً أزعم أن المصممين حسن التصرف _ يجب ألا يغيروا شكل الفصل ، بحيث يمكن تجاهلهم لأغراض التحليل الثابت. تذكر أن JS القياسي لا يحتوي على نظام نوع يعتمد عليه لإخبار التحليل بما يفعله تنفيذ الوظيفة.

  • أنا فقط حريصة على TS للحاق بالركب.

لا أرى كيف تأخرت TypeScript بأي شكل من الأشكال هنا. على الأقل وراء ماذا؟ يقوم المصممون بعملهم. هل هناك JS مكتوبة أخرى حيث تتصرف الطبقات بالطريقة التي تريدها؟

المصممون هم الآن المرحلة 2 ، ويستخدمون في كل إطار عمل JS تقريبًا.

أما بالنسبة لـ @connect ، فإن رد فعل إعادة التحميل يبلغ حوالي 2.5 مليون عملية تنزيل شهريًا ؛ من الغطرسة بشكل لا يصدق سماع أن التصميم "خاطئ" إلى حد ما لأنك كنت ستطبقه بشكل مختلف.

تؤدي المحاولات الحالية لاستخدام الأساليب التي يقدمها المصمم إلى أخطاء في الكتابة في TypeScript ، مما يتطلب حلولاً بديلة مثل دمج الواجهة ، وإضافة التعليقات التوضيحية يدويًا للطرق المضافة ، أو عدم استخدام أدوات الزخرفة لتغيير الفئات (كما لو كان أي شخص بحاجة إلى إخبارنا بأنه يمكننا فقط لا تستخدم الديكور).

هل هناك حقًا ، حقًا ، لا توجد طريقة لإبلاغ مترجم TS يدويًا بشق الأنفس أن المصمم يغير شكل الفئات؟ ربما يكون هذا جنونًا ولكن لا يمكن لـ TS فقط شحن مصمم ديكور جديداكتب يمكننا استخدامها

دعونا ج: الديكور

وإذا (وفقط إذا) تم تطبيق الديكور من هذا النوع على فصل دراسي ، فسوف يعتبر TS أن الفئة من النوع Input & {blah ()}؟

يعلم الجميع حرفيًا أنه لا يمكننا استخدام أدوات الديكور. يعرف معظمهم أيضًا أن المجموعة الصوتية تكره المصممين. يدور هذا المنشور حول كيف يمكن لـ TS ، بطريقة ما أن تجعل نظام النوع الخاص به يفهم أن المصمم يغير تعريف فئة. سيكون ذلك نعمة كبيرة لكثير من الناس.

على وجه الدقة: ألا توجد تعليقات توضيحية يمكن لـ TypeScript أن تخبرنا أن نضيفها إلى a و b و c من المثال أعلاه لكي يتعامل نظام الكتابة مع الأول بشكل لا يمكن تمييزه عن الأخير؟

نعم ، من السهل جدًا الإعلان عن أي شكل مقابل Foo تريده ، ما عليك سوى استخدام دمج الواجهة. ما عليك سوى القيام بما يلي:

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

في الوقت الحالي ، ربما تحتاج إلى توقع أي مكتبة تزيين تستخدمها لتصدير أنواع ذات معلمات تتوافق مع ما يعودون إليه ، أو ستحتاج إلى إعلان الأعضاء الذين يضيفونهم بنفسك. إذا حصلنا على # 6606 يمكنك فعل interface Foo extends typeof(a(b(c(Foo)))) .

تؤدي المحاولات الحالية لاستخدام الأساليب التي يقدمها المصمم إلى أخطاء في الكتابة في TypeScript ، مما يتطلب حلولاً بديلة مثل دمج الواجهة ، وإضافة التعليقات التوضيحية يدويًا للطرق المضافة ، أو عدم استخدام أدوات الزخرفة لتغيير الفئات (كما لو كان أي شخص بحاجة إلى إخبارنا بأنه يمكننا فقط لا تستخدم الديكور).

لم تذكر خيار تزيين الفصل الذي تكتبه عندما يكون محايدًا للأعضاء المقدمين لمصمم الديكور ، وجعله يمتد إلى فصل دراسي مزخرف عندما لا يكون كذلك. هذه هي الطريقة التي سأستخدم بها أدوات التزيين على الأقل.

هل يمكن أن تعطيني مقتطفًا من التعليمات البرمجية من مكتبة تستخدمها الآن حيث تكون هذه نقطة ألم؟

عذرًا - لم أتابع تعليقك الأخير ، لكن تعليقك قبل ذلك ، مع دمج الواجهة ، هو بالضبط كيف أتعامل معه.

يحتاج مصمم الديكور حاليًا أيضًا إلى تصدير نوع ، وأنا أستخدم واجهة مدمجة تمامًا كما أظهرت ، للإشارة إلى TS أن المصمم يضيف أعضاء جددًا. إنها القدرة على التخلص من هذا النموذج المعياري الإضافي الذي يتوق إليه الكثيرون.

arackaf ربما ما تريده حقًا هو طريقة لدمج التصريح للتفاعل مع الكتابة السياقية لوسائط الوظيفة.

بالتأكيد. أريد القدرة على استخدام مصمم في الفصل ، ومن الناحية المفاهيمية ، استنادًا إلى كيفية تعريف مصمم الديكور ، يتم دمج بعض الواجهة ، مما يؤدي إلى إضافة أعضاء إضافيين إلى الفصل الخاص بي.

ليس لدي أي فكرة عن الكيفية التي سأقوم بها في كتابة التعليقات التوضيحية لمصمم الديكور الخاص بي لتحقيق ذلك ، لكنني لست صعب الإرضاء حقًا - أي بناء جملة أعطاني إصدار TS مستقبلي للتأثير على ذلك سيجعلني سعيدًا بشكل لا يصدق :)

المصممون هم الآن المرحلة 2 ، ويستخدمون في كل إطار عمل JS تقريبًا.

وهم فقط في المرحلة الثانية وكانوا يتحركون ببطء شديد. أريد بشدة أن يحدث الديكور ، وأنا أحاول معرفة كيف يمكنني مساعدتهم على المضي قدمًا ، لكنهم لا يزالون غير مؤكد. ما زلت أعرف الأشخاص الذين قد يكون لهم تأثير على العملية والذين يعترضون عليهم ، أو يريدون تقييد التعليقات التوضيحية ، على الرغم من أنني لا أعتقد أنهم سيتدخلون. وجهة نظري حول تطبيقات الديكور المترجم الحالية ليست مواقف JS.

أما بالنسبة إلى connect ، فإن رد فعل إعادة التحميل يبلغ حوالي 2.5 مليون عملية تنزيل شهريًا ؛ من الغطرسة بشكل لا يصدق سماع أن التصميم "خاطئ" إلى حد ما لأنك كنت ستطبقه بشكل مختلف.

يرجى الامتناع عن الهجمات الشخصية مثل مناداتي بالغرور.

  1. أنا لم أهاجمك شخصيًا ، لذا لا تهاجمني شخصيًا.
  2. هذا لا يساعد حجتك على الإطلاق.
  3. لا ينبغي لشعبية المشروع استبعاده من النقد الفني.
  4. نحن نتحدث عن نفس الشيء؟ يحتوي redux-connect-decorator على 4 تنزيلات في اليوم. تبدو وظيفة رد الفعل-redux connect() مثل HOC ، كما اقترحت.
  5. أعتقد أن رأيي القائل بأن مصممي الديكور حسن التصرف لا ينبغي أن يغيروا الشكل العام للفصل ، وبالتأكيد يجب أن يستبدلوه بفصل غير ذي صلة ، هو رأي معقول جدًا وسيجد اتفاقًا كافيًا حول مجتمع JS. إنه بعيد كل البعد عن الغطرسة ، حتى لو كنت لا توافق.

تؤدي المحاولات الحالية لاستخدام الأساليب التي يقدمها المصمم إلى أخطاء في الكتابة في TypeScript ، مما يتطلب حلولاً بديلة مثل دمج الواجهة ، وإضافة التعليقات التوضيحية يدويًا للطرق المضافة ، أو عدم استخدام أدوات الزخرفة لتغيير الفئات (كما لو كان أي شخص بحاجة إلى إخبارنا بأنه يمكننا فقط لا تستخدم الديكور).

ليس الأمر أننا نقترح فقط عدم استخدام المصممين على الإطلاق ، بل أعتقد أن masaeedu يقولون إنه لأغراض تعديل نموذج أولي بطريقة آمنة من النوع ، لدينا آليات قائمة: تطبيق الوراثة والمزج. كنت أحاول أن أسأل لماذا لم يكن مثالك d مناسبًا لاستخدامه كمزيج ، ولم تعطِ إجابة إلا بالقول إنك أردت فقط استخدام الزينة.

أعتقد أن هذا جيد ، ولكن يبدو أنه يحد من المحادثة بشكل نقدي للغاية ، لذلك سأنسحب مرة أخرى.

@ justinfagnani كنت أتحدث عنه

import {connect} from 'react-redux'

connect هناك فقط وظيفة تأخذ صنفًا (مكونًا) وتخرج فئة مختلفة ، كما قلت سابقًا.

حاليًا ، في كل من Babel و TypeScript ، لدي خيار استخدام connect مثل هذا

class UnConnectedComponent extends Component {
}

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

او مثل هذا

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

أنا أفضل هذا الأخير ، ولكن إذا كنت أنت أو أي شخص آخر تفضل الأول ، فليكن ؛ العديد من الطرق تؤدي إلى روما. أنا أفضل بالمثل

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

على

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

أو

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

لم أجد صراحةً على سؤالك فقط لأنني اعتقدت أن السبب وراء استخدامي للديكور كان واضحًا ، لكنني سأصرح بذلك صراحة: أفضل بيئة العمل وزخارف بناء الجملة. هذا الخيط بأكمله يدور حول محاولة الحصول على بعض القدرة على إخبار TS كيف يقوم المصممون لدينا بتغيير الفصل المعني ، لذلك لا نحتاج إلى نموذج معياري مثل دمج الواجهة.

كما قال OP في طريق العودة عندما ، نريد فقط

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'

لمجرد العمل. حقيقة وجود بدائل أخرى لهذه البنية المحددة هي حقيقة واضحة وغير مادية. حقيقة أن الكثيرين في مجتمع JS / TS يكرهون هذا التركيب المعين هو أيضًا واضح وغير جوهري.

إذا تمكنت TS من إعطائنا أي طريقة ، مهما كانت مقيدة ، لإنجاح بناء الجملة ، فسيكون ذلك مفيدًا جدًا للغة والمجتمع.

arackaf لقد أهملت ذكر كيف أن استخدام connect يستلزم الوصول إلى أعضاء إضافيين على this . AFAICT ، من المفترض أن يكون تنفيذ المكون الخاص بك محايدًا تمامًا لما يفعله connect ، فهو يستخدم فقط props و state . وبهذا المعنى ، فإن الطريقة التي صمم بها connect تتفق مع استخدام justinfagnani للديكور أكثر من استخدامك.

ليس حقيقيا. يعتبر

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

ثم في وقت لاحق

<Foo />

يجب أن يكون ذلك صالحًا - تأتي دعائم PropsShape من متجر Redux ، لكن TypeScript لا يعرف هذا ، نظرًا لأنه يخرج عن التعريف الأصلي لـ Foo ، لذلك انتهى بي الأمر بالحصول على أخطاء للدعائم المفقودة ، وأحتاج إلى تحويلها كـ any .

ولكن لكي نكون واضحين ، فإن حالة الاستخدام المحددة التي قدمها في الأصل OP ، والمكررة في تعليقي الثاني أعلاه هنا ، شائعة للغاية في قاعدة الكود لدينا ، ومن مظهرها ، العديد من الآخرين أيضًا. نحن حرفيا نستخدم أشياء مثل

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

وعليك إضافة بعض دمج الواجهة من أجل تلبية TypeScript. سيكون من الرائع أن يكون لديك طريقة لدمج تلك الواجهة في تعريف المصمم بشفافية.

نواصل العمل في دوائر مع هذا. نتفق على أن نوع Foo يجب أن يكون نوع الإرجاع connect . نحن في اتفاق كامل على هذا.

حيث نختلف هنا فيما إذا كان الأعضاء داخل Foo يحتاجون إلى التظاهر بأن this هو نوع من الدمج العودي لنوع إرجاع الديكور و "Foo الأصلي" مهما كان ذلك يعني. أنت لم تثبت حالة استخدام لهذا.

ما نختلف فيه هو ما إذا كان الأعضاء داخل Foo بحاجة إلى التظاهر بأن هذا نوع من الدمج العودي لنوع إرجاع الديكور و "Foo الأصلي" مهما كان ذلك يعني. أنت لم تثبت حالة استخدام لهذا.

عندي. انظر تعليقي أعلاه ، ولهذه المسألة رمز OP الأصلي. هذه حالة استخدام شائعة للغاية.

يرجى توضيح ما يضيفه connect إلى النموذج الأولي الذي يتوقع منك الوصول إليه داخل المكون. إذا كانت الإجابة "لا شيء" ، فالرجاء عدم طرح connect مرة أخرى ، لأنه غير ذي صلة على الإطلاق.

masaeedu عادل بما فيه الكفاية ، وأنا أوافق. كنت أستجيب في الغالب لتأكيداتjustinfagnani حول كيفية عدم قيام مصممي الديكور بتعديل الفصل الأصلي ، تم تصميم connect بشكل سيئ ، إلخ ، إلخ.

كنت أشرح فقط التركيب اللغوي الذي أفضّله أنا والعديد من الآخرين ، على الرغم من حقيقة وجود خيارات أخرى.

في هذه الحالة ، نعم ، لا أعرف مكتبات npm الشعبية التي تأخذ فصلًا دراسيًا ، وتعيد فصلًا جديدًا بأساليب جديدة ، والتي سيتم استخدامها داخل الفصل المزين. هذا شيء أفعله أنا والآخرون كثيرًا ، في التعليمات البرمجية الخاصة بنا ، لتنفيذ عمليات المزج الخاصة بنا ، لكن ليس لدي بالفعل مثال على npm.

لكن ليس هناك خلاف حقًا في أن هذه حالة استخدام شائع ، أليس كذلك؟

arackaf لا ، ليس لأنك لا تصل إلى أي أعضاء على this تم تقديمهم بواسطة @connect . في الحقيقة أنا متأكد تمامًا من هذا المقتطف الدقيق:

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

سيتم تجميعها دون أي مشاكل في TypeScript اليوم. المقتطف الذي لصقته ليس له علاقة بالميزة التي تطلبها.

masaeedu - آسف جدًا - أدركت أنني كنت مخطئًا وحذفت هذا التعليق. ليست بالسرعة الكافية لمنعك من إضاعة الوقت بالرغم من:

arackaf لا أعرف مدى شيوع هذا النمط ، وأنا شخصياً لم أستخدمه بنفسي ولم أستخدم أي مكتبات تستخدمه. لقد استخدمت Angular 2 لفترة من الوقت ، لذا فليس الأمر كما لو أنني لم أستخدم الديكور مطلقًا في حياتي. هذا هو السبب في أنني كنت أطلب أمثلة محددة لاستخدام مكتبة حيث يمثل عدم القدرة على الوصول إلى الأعضاء الذين يقدمهم المصممون في الديكور نقطة ألم.

في كل حالة يتوقع فيها أعضاء الفصل شيئًا this ، يجب أن يكون هناك شيء ما في نص الفصل أو في بند extends للفصل الذي يلبي المتطلبات. هذه هي الطريقة التي عملت بها دائمًا ، حتى مع المصممين . إذا كان لديك مصمم يضيف بعض الوظائف التي يعتمد عليها الفصل ، فيجب على الفصل أن يمتد أيًا ما يعود المصمم ، وليس العكس. إنه مجرد منطق سليم.

لست متأكدا لماذا هذا هو الفطرة السليمة. هذا رمز من قاعدة التعليمات البرمجية الخاصة بنا حاليًا. لدينا مصمم يضيف طريقة إلى النموذج الأولي للفصل. أخطاء TypeScript بها. في الوقت الذي ألقيت فيه هذا الإعلان هناك. كان بإمكاني استخدام دمج الواجهة.

لكن سيكون من الجميل عدم استخدام أي شيء على الإطلاق. سيكون من الرائع حقًا أن تكون قادرًا على إخبار TypeScript أن "مصمم الديكور هنا يضيف هذه الطريقة إلى أي فئة يتم تطبيقها عليها - اسمح لـ this داخل الفصل بالوصول إليها أيضًا"

يبدو أن العديد من الأشخاص الآخرين في هذا الخيط يستخدمون الزخرفة بالمثل ، لذلك آمل أن تفكر في أنه قد لا يكون من المنطقي أن هذا لا ينبغي أن يعمل.

image

arackaf نعم ، لذا يجب أن تفعل export class SearchVm extends (@mappable({...}) class extends SearchVmBase {}) . يعتمد SearchVm على إمكانية التعيين ، وليس العكس.

يمتد فئة التصدير SearchVm (mappable ({...}) الفئة يمتد إلى SearchVmBase {})

بيت القصيد من هذا الموضوع هو تجنب المتداول مثل هذا. يوفر المصممون DX أجمل بكثير من الاضطرار إلى القيام بأشياء من هذا القبيل. هناك سبب لاختيارنا كتابة كود مثلما لدي ، بدلاً من ذلك.

إذا قرأت التعليقات على هذا الموضوع ، آمل أن تكون مقتنعًا بأن العديد من الأشخاص الآخرين يحاولون استخدام بناء جملة مصمم بشكل أبسط ، وبالتالي إعادة النظر في ما "ينبغي" القيام به ، أو ما هو "الفطرة السليمة" ، وما إلى ذلك.

ما تريده لا علاقة له بمصممي الديكور على وجه التحديد. مشكلتك هي أن آلية TypeScript لقول "مرحبًا TypeScript ، أشياء تحدث لهذه البنية التي لا تعرف عنها شيئًا ، دعني أخبرك كل شيء عنها" ، محدودة للغاية حاليًا. في الوقت الحالي ، لدينا فقط دمج للواجهة ، لكنك تريد أن تكون الوظائف قادرة على تطبيق دمج الواجهة على وسيطاتها ، والتي لا تتعلق على وجه التحديد بمصممي الديكور بأي شكل من الأشكال.

في الوقت الحالي ، لدينا فقط دمج للواجهة ، لكنك تريد أن تكون الوظائف قادرة على تطبيق دمج الواجهة على وسيطاتها ، والتي لا تتعلق على وجه التحديد بمصممي الديكور بأي شكل من الأشكال.

موافق. عادل بما يكفي. هل تريد مني فتح عدد منفصل ، أم تريد إعادة تسمية هذه المشكلة ، وما إلى ذلك؟

arackaf إن "boilerplate" الوحيد هو حقيقة أن لديّ class { } ، وهو أ) 7 أحرف ثابتة ، بغض النظر عن عدد الديكورات التي تستخدمها ب) نتيجة حقيقة أن المصممين لا يمكنهم (حتى الآن) ) يتم تطبيقها على التعبيرات التعسفية التي تعود بالفصل ، و ج) لأنني أردت تحديدًا استخدام المصممين لمصلحتك. هذه الصيغة البديلة:

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

ليس مطولًا أكثر مما تفعله في الأصل.

موافق. عادل بما يكفي. هل تريد مني فتح عدد منفصل ، أم تريد إعادة تسمية هذه المشكلة ، وما إلى ذلك؟

قضية منفصلة ستكون جيدة.

ليس مطولًا أكثر مما تفعله في الأصل

لم يعد الأمر مطولًا ، لكنني آمل حقًا أن تفكر في حقيقة أن الكثيرين يكرهون هذا النحو ببساطة. للأفضل أو للأسوأ ، يفضل الكثيرون عرض ديكورات DX.

قضية منفصلة ستكون جيدة.

سأكتب واحدًا غدًا ، وأرتبط بهذا المحتوى من أجل السياق.

شكرا للمساعدة في العمل من خلال هذا :)

إذا كان بإمكاني الدخول ، فأنا لا أتفق مع هذا البيان masaeedu :

ما تريده لا علاقة له بمصممي الديكور على وجه التحديد. مشكلتك هي أن آلية TypeScript لقول "مرحبًا TypeScript ، أشياء تحدث لهذه البنية التي لا تعرف عنها شيئًا ، دعني أخبرك كل شيء عنها" ، محدودة للغاية حاليًا. في الوقت الحالي ، لدينا فقط دمج للواجهة ، لكنك تريد أن تكون الوظائف قادرة على تطبيق دمج الواجهة على وسيطاتها ، والتي لا تتعلق على وجه التحديد بمصممي الديكور بأي شكل من الأشكال.

بالتأكيد يمكن أن ينظر TypeScript إلى نوع الإرجاع لمصمم الفصل لتحديد النوع الذي يجب أن يكون عليه الفصل المتحور. ليست هناك حاجة إلى "تطبيق دمج الواجهة على الوسائط". لذا كما أراها ، فإن الأمر يتعلق بالكامل بالمصممون.

أيضًا ، حالة عدم معرفة TypeScript بأن المكون المتصل لا يتطلب خصائص بعد الآن هو أحد الجوانب التي أوقفتني باستخدام Redux مع React و TypeScript.

codeandcats لقد كنت أعمل في دوائر مع هذا منذ أن بدأ الموضوع ، ولا يمكنني فعل ذلك بعد الآن. يرجى إلقاء نظرة على المناقشة السابقة بعناية ، وحاول في الواقع فهم ما كنت أختلف مع arackaf .

أوافق على أنه عندما تفعل <strong i="8">@bar</strong> class Foo { } ، يجب أن يكون نوع Foo هو نوع الإرجاع bar . لا أوافق على أن this داخل Foo يجب أن يتأثر بأي شكل من الأشكال. استخدام connect غير ذي صلة على الإطلاق بنقطة الخلاف هنا ، لأن connect لا يتوقع أن يكون المكون المزين على دراية بالأعضاء التي يضيفها إلى النموذج الأولي ويستخدمها.

يمكنني أن أفهم أنك محبط من masaeedu ولكن لا تفترض فقط لأنني لم أقم بالتعبير عن رأيي بشكل نشط في هذا الأمر ذهابًا وإيابًا الذي مررت به على مدار الـ 48 ساعة الماضية ولم أتابع المناقشة - لذا من فضلك اعفيني من رفيق التعاطف.

كما أفهمها ، تعتقد أنه داخل فصل دراسي مزخرف ، لا ينبغي أن يعرف عن أي طفرة حدثت على نفسه بواسطة مصممي الديكور ، لكن يجب أن يكون خارجها. أنا لا أختلف مع ذلك. حقيقة أن arackaf يعتقد أن داخل الفصل يجب أن يرى النسخة المعدلة هي بجانب تعليقي.

في كلتا الحالتين ، يمكن أن يستخدم TypeScript النوع الذي يتم إرجاعه بواسطة وظيفة التزيين كمصدر الحقيقة للفصل. ومن ثم فهي قضية Decorator ، وليست بعض وظائف طفرة الجدل الغريبة الجديدة كما تقترح ، والتي تبدو بصراحة وكأنها محاولة لرجل قش.

Zalastax الذي يستخدم وظيفة الديكور كوظيفة رغم ذلك ، وليس كمصمم. إذا كان لديك العديد من الديكورات التي تحتاج إلى تطبيقها ، فأنت بحاجة إلى تداخلها ، وهو ما لم يعد مطولًا من الناحية الفنية ولكنه لم يعد ممتعًا من الناحية التركيبية - هل تعرف ما أعنيه؟

التنازل الخ

ضوضاء.

حقيقة أن arackaf يعتقد أن داخل الفصل يجب أن يرى النسخة المعدلة هي بجانب تعليقي.

إنها النقطة المركزية في الفقرة التي ترد عليها ، لذا إذا كنت تتحدث عن شيء آخر فهذا غير ذي صلة. ما يريده arackaf هو ، تقريبًا ، دمج الواجهة في وسيطات الوظيفة. تتم معالجة هذا بشكل أفضل من خلال ميزة مستقلة عن المصممين. ما تريده (والذي يبدو مطابقًا لما أريده) هو إصلاح الخلل في TypeScript حيث لا ينتج عن <strong i="12">@foo</strong> class Foo { } نفس النوع من التوقيع لـ Foo مثل const Foo = foo(class { }) . فقط الأخير مرتبط بشكل خاص بالمصممون.

كما أفهمها ، تعتقد أنه داخل فصل دراسي مزخرف ، لا ينبغي أن يعرف عن أي طفرة حدثت على نفسه بواسطة مصممي الديكور ، لكن يجب أن يكون خارجها. أنا لا أختلف مع ذلك

إذا كنت قد قررت التحدث عن شيء ما نحن متفقون عليه ، لكن ابدأ بعبارة "أنا لا أتفق مع هذا البيان" ، فأنت بذلك تضيع الوقت فقط.

هل يمكنني الحصول على بعض البطاطس مع هذا الملح؟

أوه أرى ، عندما ترعى زملائك فهذا أمر جيد ، لكن إذا جذبك الناس إليه ، فهذه ضوضاء.

لا أفهم سبب الحاجة إلى مفهوم "طفرة النقاش" الخاص بك لتنفيذ ما اقترحه arackaf .

بغض النظر عما إذا كنا نتفق مع arackaf أم لا ، في كلتا الحالتين يمكن لـ TypeScript ببساطة أن يستنتج النوع الفعلي من نتيجة الديكور ، في رأيي المتواضع.

أعتذر إذا شعرت بالتعالي ؛ كان القصد هو إيصالك إلى
في الواقع خيط متجول عملاق يمكن أن يغفر لأي شخص عنه
قراءة سريعه. أنا أؤيد هذا ، لأنه لا يزال من الواضح أنك لست على علم
من التفاصيل و "يختلفون" معي بناءً على سوء فهم
موقعي.

على سبيل المثال ، سؤالك عن سبب دمج الواجهة في الوظيفة
الحجج التي تحل مشكلة آدم ، وأفضل إجابة لها هي النظر إلى الأمور ذات الصلة
مناقشة مع آدم ، حيث يقول إنه يستخدم بالفعل دمج الواجهة ،
لكنه يجد أنه من المزعج القيام بذلك في كل موقع إعلان.

أعتقد أن جميع الجوانب التقنية لهذا تم ضربها حتى الموت. أنا
لا تريد الخيط أن تهيمن عليه الخلافات الشخصية ، لذلك هذا
سيكون آخر مشاركة لي.

هذا صحيح ، هناك نوعان من الأخطاء مع المصممين: لا يتم احترام نوع الإرجاع ، وداخل الفصل ، لا يتم احترام الأعضاء المضافين ( this داخل الفصل). ونعم ، أنا مهتم أكثر بالأخير ، حيث يسهل التعامل مع الأول.

لقد فتحت حالة منفصلة هنا: https://github.com/Microsoft/TypeScript/issues/16599

مرحبًا بالجميع ، لقد فاتني هذه المحادثة خلال عطلة نهاية الأسبوع ، وربما لا يوجد سبب كبير لإعادتها مرة أخرى ؛ لكني أريد أن أذكر الجميع أنه في خضم هذه الأنواع من المناقشات ، يكون كل شخص يزن عادة حسن النية. يمكن أن يساعد الحفاظ على نبرة محترمة في توضيح وتوجيه المحادثة ، ويمكن أن يعزز المساهمين الجدد. إن القيام بذلك ليس بالأمر السهل دائمًا (خاصة عندما يترك الإنترنت نغمة الصوت للقارئ) ، لكنني أعتقد أنه مهم للسيناريوهات المستقبلية. 😃

ما هو الوضع على هذا؟

@ alex94puchades لا يزال اقتراح المرحلة 2 ، لذلك ربما لا يزال لدينا بعض الوقت. يبدو أن TC39 لديه بعض الحركة عليه ، على الأقل.

وفقًا لهذا التعليق ، يبدو أنه يمكن اقتراحه للمرحلة 3 في وقت مبكر من نوفمبر.

طريقة بديلة لتغيير توقيع الوظيفة بواسطة المصمم

إضافة وظيفة wapper فارغة

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

أضف تعريفًا

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

نتيجة

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

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

/ بينغ

إذا كان أي شخص يبحث عن حل لهذا ، فإن أحد الحلول التي توصلت إليها هو أدناه.

إنه ليس الحل الأفضل لأنه يتطلب كائنًا متداخلًا ، لكنه يعمل بشكل جيد بالنسبة لي لأنني أردت بالفعل أن تكون خصائص المصمم في كائن وليس فقط في حالة الفصل.

هذا شيء أستخدمه لشروط Angular 5 الخاصة بي.

تخيل أن لدي مصمم زخرفة @ModalParams(...) يمكنني استخدامه في ConfirmModalComponent المخصص. لكي تظهر خصائص مصمم الديكور @ModalParams(...) ضمن المكون المخصص الخاص بي ، أحتاج إلى توسيع فئة أساسية بها خاصية سيقوم المصمم بتعيين قيمها لها.

على سبيل المثال:

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 
    }
}

مرة أخرى ، إنها ليست جميلة جدًا ، لكنها تعمل بشكل جيد لحالة الاستخدام الخاصة بي ، لذلك اعتقدت أن شخصًا آخر قد يجدها مفيدة.

lansana لكنك لا تحصل على الأنواع أليس كذلك؟

confraria للأسف لا ، ولكن قد تكون هناك طريقة لتحقيق ذلك إذا قمت بتطبيق فئة Modal عامة تقوم بتمديدها. على سبيل المثال ، قد يعمل شيء كهذا (لم يتم اختباره):

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

: / نعم ولكن بعد ذلك يتم فصل النوع عن المصمم وبالتأكيد لا يمكنك استخدام اثنين منهم .. :( إلى جانب ذلك ، ستحصل على أخطاء إذا لم ينفذ الفصل الطرق .. :( لا أعتقد ذلك هي طريقة للحصول على الأنواع الصحيحة مع هذا النمط في الوقت الحالي

نعم سيكون رائعًا إذا كان هذا ممكنًا ، مما يجعل TypeScript أفضل وأكثر تعبيرًا. نأمل أن يأتي شيء ما قريبا.

lansana نعم ، النقطة المهمة هي أنه سيكون من الجيد لمصممي الفصل أن يكونوا قادرين على تغيير توقيع الفصل بمفردهم ، دون مطالبة الفصل بتوسيع أو تنفيذ أي شيء آخر (نظرًا لأن هذا تكرار للجهد وكتابة المعلومات) .

ملاحظة جانبية: في المثال الخاص بك هناك ، ضع في اعتبارك أن params سيكون ثابتًا عبر جميع مثيلات فئات المكونات الشرطية المزينة ، نظرًا لأنها مرجع كائن. على الرغم من أنه ربما يكون ذلك عن طريق التصميم. : -) لكني استطرادا ...

تحرير: عندما أفكر في هذا ، يمكنني رؤية سلبيات تمكين مصممي الديكور من تعديل توقيعات الفصل. إذا كان تطبيق الفصل يتميز بوضوح بنوع معين من التعليقات التوضيحية ، لكن المصمم قادر على الانقضاض على كل ذلك وتغييره ، فسيكون ذلك بمثابة خدعة بسيطة للعب على المطورين. من الواضح أن الكثير من حالات الاستخدام تتعلق بدمج منطق جديد للفئة ، لذلك لسوء الحظ يمكن تسهيل العديد منها بشكل أفضل من خلال تطبيق الامتداد أو الواجهة - والذي ينسق أيضًا مع توقيع الفئة الحالي ويثير الأخطاء المناسبة عند حدوث الاصطدامات. يستخدم إطار عمل مثل Angular بالطبع استخدامًا غزيرًا لمصممي الديكور لزيادة الفصول الدراسية ، لكن التصميم لا يسمح للفصل "باكتساب" أو مزج منطق جديد من المصمم الذي يمكن استخدامه بعد ذلك في التنفيذ الخاص به - إنه عزل منطق الطبقة من منطق تنسيق الإطار. هذا رأيي المتواضع ، على أي حال. : -)

يبدو من الأفضل استخدام فئات أعلى مرتبة بدلاً من أدوات الديكور + المتسللين. أعلم أن الأشخاص يريدون استخدام المصممين لهذه الأشياء ولكن استخدام التركيب + المكوّنات ذات الترتيب الأعلى هو السبيل للذهاب ومن المحتمل أن يظل على هذا النحو ... إلى الأبد ؛) وفقًا لـ MS إلخ. تحقق من المستخدمين الكبار لمصممي الديكور مثل Angular ستلاحظ أنها تستخدم فقط بهذه السعة. أشك في قدرتك على إقناع المشرفين على TypeScript بخلاف ذلك.

إنه لأمر محزن وغريب بعض الشيء أن مثل هذه الميزة القوية ، التي من شأنها أن تسمح بتكوين ميزة حقيقية ، والتي تولد مثل هذه المشاركة قد تجاهلها فريق TS لفترة طويلة الآن.

هذه حقًا ميزة يمكن أن تحدث ثورة في كيفية كتابة الكود ؛ السماح للجميع بإصدار مزيج صغير على أشياء مثل Bitly أو NPM وإعادة استخدام كود رائع حقًا في Typescript. في مشاريعي الخاصة ، سأجعل على الفورPoolableInitableTranslating وربما كومة أكثر.

يرجى كل فريق TS الأقوياء. "كل ما تحتاجه" لتنفيذه هو تكريم الواجهات التي تم إرجاعها.

// 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
        }
    }
}

مما يسمح بتشغيل هذا الرمز دون شكوى:

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

تضمين التغريدة

على الرغم من أنني أتفق مع نقاطك ، إلا أنه يمكنك تحقيق نفس الشيء مثل:

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

وبعد ذلك لن تحتاج حتى إلى وظيفة init ، يمكنك فقط القيام بذلك:

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

تضمين التغريدة
نعم ، لكن هذا سيؤثر على المنشئ الخاص بي الذي لا يكون دائمًا مخادعًا للغاية ما أريده.

لدي أيضًا مزيج مماثل لجعل أي كائن قابل للتجميع وأشياء أخرى. ما هو مطلوب فقط هو إمكانية إضافة وظائف من خلال التكوين. يعد مثال init مجرد طريقة تحتاج إلى القيام بما قمت به دون التأثير على المُنشئ ، مما يجعله مفيدًا مع أطر أخرى.

AllNamesRTaken ليس هناك فائدة من لمس المصممين في الوقت الحالي كما هو الحال في هذه الحالة لفترة طويلة والاقتراح في المرحلة 2 بالفعل. انتظر حتى انتهاء https://github.com/tc39/proposal-decorators ومن ثم سنحصل عليها على الأرجح بأي شكل يتفق عليه الجميع.

تضمين التغريدة
أنا أختلف معك بشدة لأن ما أطلبه يتعلق فقط بالنوع وليس الوظيفة. ومن ثم فإنه ليس له علاقة كبيرة بالأشياء ES. يعمل الحل أعلاه بالفعل ، ولا أريد أن أضطر إلى الإرسال إليه.

AllNamesRTaken يمكنك فعل هذا _today_ مع mixins بدون أي تحذيرات على الإطلاق:

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

في المستقبل ، إذا حصلنا على اقتراح الخلطات في JS ، فيمكننا القيام بذلك:

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 هي الطريقة التي بدأت بها هذه الميزات ، كما أن تطبيقي يعمل أيضًا كما وصفت:

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

وهو أمر لطيف ، كحل بديل ، ولكنه لا يزيل المزايا من السماح لمصممي الديكور بتغيير النوع في رأيي.

EDIT: يفقد أيضًا الكتابة على الجزء الخاص بـ init.

باستخدام هذه الميزة ، ستتمكن من كتابة HoC بسهولة أكبر
آمل أن يتم إضافة هذه الميزة

kgtkr هذا هو السبب الرئيسي لرغبتي في هذا بشدة ...

هناك أيضًا حالة طوارئ صغيرة في تعريفات react-router لأن بعض الأشخاص قرروا أن أمان النوع أكثر أهمية من وجود واجهة تزيين الفصل. السبب الأساسي هو أن withRouter يجعل بعض الدعائم اختيارية.

الآن يبدو أن هناك عداء ، حيث يضطر الناس إلى استخدام واجهة دالة withRouter بدلاً من الزخرفة .

إن البدء في حل هذه الميزة سيجعل العالم مكانًا أكثر سعادة.

حدث نفس العداء * لفترة من الوقت مع كتابات واجهة المستخدم المادية و withStyle ، والتي تم تصميمها لاستخدامها كديكور ، ولكن من أجل استخدامها بطريقة آمنة ، يحتاج مستخدمو TypeScript إلى استخدامها كوظيفة عادية. على الرغم من أن الغبار قد استقر في الغالب على ذلك ، إلا أنه مصدر ارتباك مستمر للقادمين الجدد إلى المشروع!

* حسنًا ، قد تكون كلمة "عداء" كلمة قوية

لقد كنت أشاهد هذا لفترة طويلة ، وحتى وصوله يمكن للآخرين الاستفادة من الاختراق الصغير الخاص بي لتحقيق هذا السلوك بطريقة متوافقة مع المستقبل. إذن ها هو ، تنفيذ فئة حالة نمط سكالا الأساسية الفائقة copy طريقة ...

لدي مصمم الديكور كما يلي:

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

ينشئ هذا الرمز فئة مجهولة مع إرفاق طريقة copy . إنه يعمل تمامًا كما هو متوقع في JavaScript عندما يكون في بيئة تدعم الديكور.

لاستخدام هذا في TypeScript ، ولجعل نظام الكتابة يعكس الطريقة الجديدة في الفئة المستهدفة ، يمكن استخدام الاختراق التالي:

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

ستكشف جميع مثيلات MyCaseClass كتابات طريقة copy الموروثة من الفئة المجهولة داخل المصمم CaseClass . وعندما يدعم TypeScript طفرة الأنواع المُعلنة ، يمكن تعديل هذا الكود بسهولة إلى صيغة المصمم المعتادة <strong i="18">@CaseClass</strong> etc بدون أي إشارات ضوئية غير متوقعة.

سيكون من الرائع رؤية هذا في الإصدار الرئيسي التالي من TypeScript - أعتقد أنه سيساعد في ترميز أكثر وضوحًا وإيجازًا بدلاً من تصدير فوضى غريبة لفئات الوكيل.

متى ستتوفر هذه الميزة؟

كنت أرغب في استخدام مصمم ديكور لأداء المهام المتكررة.
ولكن الآن عند تهيئة الفصل ، يظهر لي الخطأ التالي: 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.

آمل أن تكون هذه الميزة متاحة قريبًا! :)

@ iainreid820 هل واجهت أي أخطاء غريبة باستخدام هذا النهج؟

الانتظار الطويل! في غضون ذلك ، هل يتم حل هذا بأي شيء على خريطة الطريق الحالية؟
مثل قضية 5453 ؟

أنا أستخدم واجهة المستخدم المادية وكان علي فقط أن أدرك أن TypeScript لا يدعم صيغة الزخرفة لـ withStyles : https://material-ui.com/guides/typescript/#decorating -components

الرجاء إصلاح هذا القيد في إصدار TypeScript التالي. يبدو مصممو الصفوف عديمي الفائدة بالنسبة لي الآن.

بصفتك مشرفًا على Morphism Js ، يعد هذا قيدًا كبيرًا لهذا النوع من المكتبات. أحب أن أتجنب مستهلك مصمم الوظيفة أن يضطر إلى تحديد النوع المستهدف للوظيفة https://github.com/nobrainr/morphism# --toclassobject-decorator ، وإلا فإن استخدام الزخارف بدلاً من HOF يبدو وكأنه عديم الفائدة بعض الشيء 😕
هل هناك أي خطة لمعالجة هذا؟ هل هناك طريقة للمساعدة في تحقيق هذه الميزة؟ شكرا لكم مقدما!

bikeshedder أن مثال واجهة المستخدم المادية هو حالة استخدام مختلط للفئة ، ويمكنك الحصول على النوع الصحيح من mixins. بدلا من:

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

اكتب:

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

justinfagnani هذا لا يعمل بالنسبة لي:

ss 2018-10-16 at 10 00 47

هذا هو الكود الخاص بي: https://gist.github.com/G-Rath/654dff328dbc3ae90d16caa27a4d7262

@ G-Rath أعتقد أن new () => React.Component<Props, State> بدلاً من ذلك يجب أن يعمل؟

تضمين التغريدة لقد قمت بتحديث المضمون بالرمز الجديد ، لكن هل هذا ما قصدته؟

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

بغض النظر عن كيفية تنسيقه ، لا يبدو extends withStyles(styles)(...) اقتراحًا مناسبًا بشكل أساسي. withStyles ليس فئة مختلطة.

يأخذ withStyles فئة المكوّن A وينشئ فئة B والتي عند تقديمها تصيير فئة A وتمرير الدعائم إليها + الخاصية classes .

إذا قمت بتوسيع قيمة الإرجاع لـ withStyles ، فأنت تقوم بتوسيع الغلاف B ، بدلاً من تنفيذ فئة A التي تتلقى في الواقع الخاصية classes .

بغض النظر عن كيفية تنسيقه ، فإنه يمتد مع الأنماط (الأنماط) (...) لا يبدو اقتراحًا مناسبًا ، بشكل أساسي. withStyles ليس فئة مختلطة.

بالفعل. لست متأكدًا من سبب وجود الكثير من المعارضة هنا.

ومع ذلك ، فإن مصممي الديكور قريبون للغاية من المرحلة 3 ، مما أسمعه ، لذلك أتخيل أن TS ستحصل على الدعم المناسب هنا قريبًا.

أسمع الناس الذين يريدون بناء جملة أنظف رغم ذلك ، esp. عند استخدام تطبيق HoCs متعددة كديكور. الحل المؤقت الذي نستخدمه هو لف أدوات تزيين متعددة داخل وظيفة الأنابيب ثم استخدام هذه الوظيفة النقية لتزيين المكون. على سبيل المثال

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

حيث flow هو lodash.flow . توفر الكثير من مكتبات الاستخدام شيئًا كهذا - recompose ، Rx.pipe إلخ. ولكن يمكنك بالطبع كتابة دالة الأنبوب البسيطة الخاصة بك إذا كنت لا ترغب في تثبيت مكتبة.

أجد هذا أسهل في القراءة من عدم استخدام أدوات الزينة ،

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

سبب آخر لاستخدام هذا ، هو أن هذا النمط يمكن العثور عليه بسهولة واستبداله / grep بمجرد الإعلان عن مواصفات الديكور ودعمها بشكل صحيح.

justinfagnani الغريب أنني أحصل على خطأ في بناء الجملة من ESLint عندما أحاول تغيير extends React.Component<Props, State> إلى extends withStyles(styles)(React.Component<Props, State>) ، لذلك لم أتمكن من التحقق من خطأ @ G-Rath بنفس الطريقة.

لكنني لاحظت أنه إذا فعلت شيئًا مثل التالي ، فستظهر مشكلة في new (ربما نفس المشكلة؟):

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

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

والخطأ هو:

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

هل هذا يعني أن النوع الذي تم إرجاعه بواسطة Material UI ليس مُنشئًا (على الرغم من أنه ينبغي أن يكون كذلك)؟

@ sagar-sm ولكن ، هل تحصل على النوع الصحيح مدعومًا بنوع واجهة المستخدم المادية؟ لا يبدو أنك ستفعل.

للرجوع إليها ، تعثرت في هذا لأنني كنت أحاول أيضًا استخدام Material UI's withStyles كديكور ، والذي لم ينجح ، لذلك طرحت هذا السؤال: https://stackoverflow.com/questions/53138167

بحاجة إلى هذا ، ثم يمكننا جعل الدالة غير المتزامنة ترجع إلى صورة بلوبيرد

حاولت أن أفعل شيئًا مشابهًا. أستخدم أجهزة الصراف الآلي التالية الحل البديل:

أولاً ، هذا هو مصمم الديكور "mixin" الخاص بي

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>
        }
    }
}

إنشاء ديكور من الواجهة

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

وهذه هي الطريقة التي أستخدمها:

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

تكمن الحيلة أيضًا في إنشاء واجهة تحمل نفس اسم الفئة لإنشاء إعلان مدمج.
لا يزال الفصل يظهر فقط كنوع Test ولكن عمليات التحقق من الكتابة تعمل.
إذا كنت ستستخدم المصمم بدون @ -Notation ، يمكنك ببساطة استدعاءها كوظيفة ، فستحصل على نوع التقاطع الصحيح ولكنك تفقد القدرة على فحص النوع داخل الفصل نفسه نظرًا لأنه لا يمكنك استخدام خدعة الواجهة بعد الآن وتبدو أكثر بشاعة. على سبيل المثال:

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)

ماذا تقول عن هذه الأساليب؟ إنه ليس جميلًا ولكنه يعمل بقدر ما هو حل بديل.

هل هناك أي تقدم في هذه القضية؟ تبدو هذه ميزة قوية للغاية من شأنها أن تفتح مجموعة كبيرة من الاحتمالات المختلفة. أفترض أن هذه المشكلة قديمة في الغالب الآن لأن المصممين لا يزالون قيد التجربة؟

سيكون من الرائع حقًا الحصول على بيان رسمي بشأن @ andy- msahejlsbergsandersn . 🙏

من المحتمل أننا لن نفعل أي شيء هنا حتى يتم الانتهاء من الديكور.

DanielRosenwasser - ماذا تعني "اللمسات الأخيرة" هنا؟ هل المرحلة 3 في TC39 مؤهلة؟

لم أدرك أنهم وصلوا إلى هذا الحد! متى وصلوا إلى المرحلة الثالثة؟ هل هذا حديث؟

لم يفعلوا بعد. أعتقد أنهم على استعداد للتقدم من المرحلة 2 إلى المرحلة 3 مرة أخرى في اجتماع TC39 لشهر يناير.

يمكنك مراقبة جدول الأعمال للحصول على التفاصيل.

صحيح (على الرغم من أنني لا أستطيع أن أؤكد أن الأمر متروك للتقدم في يناير). كنت أسأل فقط ما إذا كانت المرحلة 3 ستتأهل عندما يصلون إلى هناك.

في رأيي ، لا يزال اقتراح مصمم الديكور يحتوي على العديد من المشكلات الجادة ، لذلك إذا تقدم إلى المرحلة 3 في يناير ، فسوف يرتكب الخطأ مرة أخرى تمامًا مثل اقتراح حقول الفصل الإشكالية والاقتراح العالمي.

hax هل يمكن أن توسع؟
كنت أرغب حقًا في ذلك وأجد أن الافتقار إلى التواصل بشأن هذه المسألة محزن ولم أسمع عن المشاكل لسوء الحظ.

AllNamesRTaken تحقق من قائمة قضية اقتراح الديكور. 😆 على سبيل المثال ، export قبل / بعد وسيطة الزخرفة هي مانع للمرحلة 3.

هناك أيضًا بعض التغييرات المقترحة مثل إعادة تصميم واجهة برمجة التطبيقات والتي أعتقد أنها تعني أن الاقتراح غير مستقر للمرحلة 3.

كما أنها تتعلق بمقترح الحقول الصفية الإشكالية. على الرغم من وصول حقول الفصل إلى المرحلة 3 ، إلا أن هناك العديد من المشكلات. مشكلة كبيرة أشعر بالقلق هي أنها تترك العديد من القضايا لمصممي الديكور (على سبيل المثال ، محمية) وهذا أمر سيء لكلا الاقتراحين.

ملاحظة ، أنا لست مفوضًا لـ TC39 ، وبعض مندوبي TC39 لا يوافقون أبدًا على تعليقاتي حول الوضع الحالي للعديد من المشكلات. (على وجه الخصوص لدي رأي قوي بأن عملية TC39 الحالية بها إخفاقات كبيرة في العديد من القضايا المثيرة للجدل. تأجيل بعض المشكلات لمصممي الديكور لا يحل المشكلة أبدًا ، فقط اجعل اقتراح الديكور أكثر هشاشة.)

أعتقد أنه سيتم تحديد هذه الأمور وسيكون الاقتراح على ما يرام ، لكنني أفضل ألا نناقش حالة الاقتراح هنا.

أعتقد أن المرحلة 3 بثقة كافية أو المرحلة 4 هي على الأرجح المكان الذي سنقوم فيه بتنفيذ الاقتراح الجديد ، ومن ثم يمكننا النظر في هذه المشكلة.

شكرا دانيال! عادةً ما تشير المرحلة 3 إلى وجود ثقة قوية بالرقم 4 ، لذلك تتقاطع الأصابع مع اجتماع يناير.

وشكرا على الخروج من مناقشة الديكور هنا. إنه لأمر غريب مستوى الغضب وسفك الدراجة الذي تسببت فيه هذه الميزة. لم أرَ شيئًا كهذا أبدًا

للتسجيل فقط ، كان هناك طلب ميزة حول نفس الشيء: https://github.com/Microsoft/TypeScript/issues/8545

مزعج بدرجة كافية تدعم TypeScript هذه الميزة إلى حد ما عندما تقوم بالتجميع من JavaScript (https://github.com/Microsoft/TypeScript/wiki/What٪27s-new-in-TypeScript#better-handling-for-namespace-patterns-in -js- ملفات):

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

وحتى بالنسبة إلى رمز TypeScript نفسه ولكن فقط عندما يتعلق الأمر بالوظائف (!) (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);
}

DanielRosenwasserarackaf أي كلمة حول الانتقال إلى المرحلة 3؟ إنني أتطلع إلى إنشاء مكتبة تضيف وظائف النموذج الأولي إلى الفصل الدراسي عند استخدام المصمم.

لم يتقدموا في الاجتماع الأخير TC39 ؛ قد يكونون على استعداد للتقدم مرة أخرى في الاجتماع القادم. ومع ذلك ، من غير الواضح. هناك الكثير في الهواء حول الاقتراح في هذه المرحلة.

يُنصح الأشخاص المهتمون هنا بتتبع الاقتراح نفسه - مما سيبقي الضوضاء منخفضة لأولئك منا الذين يشاهدون الخيط بالإضافة إلى مشرفين على TS.

أي تحديثات على هذه؟

الحل البديل (الزاوية :)) 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
    }
}

ما رأيك في هذا الحل السهل "في غضون ذلك"؟
https://stackoverflow.com/a/55520697/1053872

تستخدم Mobx-state-tree أسلوبًا مشابهًا مع الوظيفة cast() . لا أشعر بالرضا ولكن ... كما أنه لا يزعجني كثيرًا. من السهل إخراجها عندما يأتي شيء أفضل.

أود فقط أن أكون قادرًا على فعل شيء على غرار هذا ❤️
أليست هذه هي القوة التي يفترض أن يتمتع بها المصممون؟

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

قضيت بعض الوقت في إنشاء حل بديل لكتابة خصائص الفصل المزخرفة. لا أعتقد أنه سيكون حلاً قابلاً للتطبيق للسيناريوهات في هذا الموضوع ، ولكن قد تجد بعض أجزاء منه مفيدة. إذا كنت مهتمًا ، يمكنك التحقق من رسالتي على Medium

أي تحديثات بشأن هذه المسألة؟

كيف نفعل مع هذا؟

ما نتيجة هذه القضية وكيف تعمل الآن؟

يمكنك استخدام فئات mixin للحصول على هذا التأثير
https://mariusschulz.com/blog/mixin-classes-in-typescript

Bnaya الذي ليس ما نريده الآن هو ؛).
يسمح لنا المصممون بتجنب إنشاء فصول دراسية لم نعتزم استخدامها أبدًا كما كنا نصنع جافا للمدرسة القديمة. سيسمح المصممون ببنية تكوين نظيفة ومرتبة للغاية حيث يمكن للفصل الاستمرار في أداء الوظائف الأساسية والحصول على وظائف عامة إضافية تتكون عليها. نعم mixins يمكن أن تفعل ذلك لكنها ضمادة.

تغير اقتراح مصمم الديكور بشكل كبير في الأشهر الماضية - من غير المحتمل للغاية أن يستثمر فريق TS أي وقت في التنفيذ الحالي الذي سيكون غير متوافق قريبًا ™.

أوصي بمشاهدة الموارد التالية:

اقتراح الديكور: https://github.com/tc39/proposal-decorators
خارطة طريق TypeScript: https://github.com/microsoft/TypeScript/wiki/Roadmap

Bnaya الذي ليس ما نريده الآن هو ؛).
يسمح لنا المصممون بتجنب إنشاء فصول دراسية لم نعتزم استخدامها أبدًا كما كنا نصنع جافا للمدرسة القديمة. سيسمح المصممون ببنية تكوين نظيفة ومرتبة للغاية حيث يمكن للفصل الاستمرار في أداء الوظائف الأساسية والحصول على وظائف عامة إضافية تتكون عليها. نعم mixins يمكن أن تفعل ذلك لكنها ضمادة.

ليس الاختلاط ، بل فئة.
انها ليست نسخة سيئة مزيج الاشياء
عندما يصل مشغل خط الأنابيب ، ستكون البنية معقولة أيضًا

يمكنك استخدام فئات mixin للحصول على هذا التأثير

يمكن أن تكون الخلطات المصنّعة من فئة المصانع مصدر إزعاج في TypeScript ؛ حتى لو لم تكن كذلك ، فهي متداخلة للغاية من خلال مطالبتك بلف الفئات في وظيفة للتحويل إلى mixin ، وهو الأمر الذي لا يمكنك فعله في بعض الأحيان إذا كنت تقوم باستيراد فئات تابعة لجهات خارجية.

ما يلي أفضل بكثير وأبسط وأنظف ويعمل مع فئات الجهات الخارجية المستوردة. لقد عملت بشكل جيد في جافا سكريبت عادي مع عدم وجود ترشيح بخلاف نقل المصمم ذي الطراز القديم ، لكن class es يبقى أصليًا بالكامل 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()

والإخراج في Chrome هو:

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

يمنحك هذا تمامًا ما تفعله خلطات المصانع ، بدون كل نموذج. لم تعد هناك حاجة إلى أغلفة الوظائف حول الفصول الدراسية الخاصة بك.

ومع ذلك ، كما تعلم ، لا يستطيع المصمم تغيير نوع الفئة المحددة. 😢

لذلك ، في الوقت الحالي ، يمكنني القيام بما يلي ، مع التحذير الوحيد أن الأعضاء المحميين غير قابلين للوراثة:

// `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) }
}

يتم توضيح المشكلة المتعلقة بفقدان الأعضاء المحميين بهذين المثالين لتطبيقات multiple (فيما يتعلق بالأنواع ، ولكن تم حذف تنفيذ وقت التشغيل للإيجاز):

أرغب حقًا في طريقة لتعيين الأنواع بما في ذلك الأعضاء المحميين.

سيكون هذا مفيدًا حقًا بالنسبة لي لأن لديّ مصمم ديكور يسمح باستدعاء فصل دراسي كوظيفة.

هذا يعمل:

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

هذا لا يعمل:

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

يمكن إجراء حل بديل غير صعب التنظيف عند ظهور هذه الميزة باستخدام دمج التصريح وملف تعريف الأنواع المخصصة.

بعد هذا بالفعل:

// 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 {
  // ...
}

أضف الملف التالي (الملف المراد إزالته بمجرد ظهور الميزة):

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

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

حل آخر ممكن

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

بدأت في عام 2015 وما زالت معلقة. هناك العديد من المشكلات ذات الصلة وحتى المقالات مثل هذا https://medium.com/p/caf24aabcb59/responses/show ، في محاولة لإظهار الحلول (التي تعتبر صعبة بعض الشيء ، ولكنها مفيدة)

هل يمكن لشخص أن يلقي بعض الضوء على هذا. هل تم اعتباره للمناقشة الداخلية حتى الآن؟

TL ؛ dr - هذه الميزة محشورة في TC39. لا يمكن إلقاء اللوم على TS على هذا على الإطلاق. إنهم لن ينفذوا حتى يصبح المعيار.

لكن هذه هي Microsoft ، يمكنهم فقط تنفيذها ثم قولوا للمعايير - "انظر ، الناس يستخدمون نسختنا بالفعل": trollface:

تكمن المشكلة هنا في أن اقتراح المصممين قد تغير _ بشكل ملحوظ _ (بطرق غير متوافقة مع الإصدارات السابقة) منذ أن نفذت TypeScript أدوات التزيين. سيؤدي هذا إلى موقف مؤلم عندما يتم الانتهاء من المصممين الجدد وتنفيذهم ، حيث يعتمد بعض الأشخاص على سلوك ودلالات التزيين الحالية لـ TypeScript. أظن بشدة أن فريق TypeScript يريد تجنب اتخاذ إجراء بشأن هذه المشكلة لأن القيام بذلك من شأنه أن يشجع على المزيد من استخدام أدوات الديكور الحالية غير القياسية ، مما سيجعل الانتقال إلى أي تطبيق ديكور جديد أكثر إيلامًا في النهاية. بشكل أساسي ، أنا شخصياً لا أرى حدوث ذلك.

بالمناسبة ، قمت مؤخرًا بإنشاء # 36348 ، وهو اقتراح لميزة من شأنها أن توفر حلاً بديلاً قويًا. لا تتردد في إلقاء نظرة / إبداء الرأي حول / وحشية. 🙂

TL ؛ dr - هذه الميزة محشورة في TC39. لا يمكن إلقاء اللوم على TS على هذا على الإطلاق. إنهم لن ينفذوا حتى يصبح المعيار.

بالنظر إلى هذه الحقيقة ، قد يكون من الحكمة أن يكتب فريق التطوير شرحًا سريعًا وتحديثًا للحالة ، ويغلق محطة التوصيل هذه حتى TC39 لتحريك هذا للأمام إلى المرحلة المطلوبة؟

هل لأي شخص يقرأ هذا أي وزن في هذا الموضوع؟

في الواقع ، يتم تعليق كل محادثة متعلقة بمصممي الديكور في الهواء. مثال: https://github.com/Microsoft/TypeScript/issues/2607

سيكون الحصول على بعض الوضوح حول مستقبل المصممين مفيدًا ، حتى لو كان يتعلق بمطالبة المساهمين بتنفيذ الوظائف المطلوبة. بدون توجيه واضح ، يكون مستقبل المصممين ضبابيًا

أود لو تمكن فريق TypeScript من الاضطلاع بدور استباقي في اقتراح مصمم الديكور ، على غرار الطريقة التي ساعدوا بها في الحصول على تسلسل اختياري. أرغب أيضًا في المساعدة ، لكنني لم أعمل في تطوير اللغة بعد ، وبالتالي لست متأكدًا من أفضل طريقة للقيام بذلك: - /

لا يمكنني التحدث نيابة عن الفريق بأكمله ، و TypeScript كمشروع - لكنني أعتقد أنه من غير المحتمل أن نقوم بأي عمل مصمم جديد حتى يتم تسويته وتأكيده ، بالنظر إلى كيف تباعد تنفيذ الديكور بالفعل مرة واحدة عن تنفيذ TypeScript.

لا يزال الاقتراح قيد التطوير ، وقد تم طرحه في TC39 هذا الأسبوع https://github.com/tc39/proposal-decorators/issues/305

orta في هذه الحالة أعتقد أنه يجب تحديث المستندات الخاصة بمصمم الفصل بعد ذلك. إنها تنص على أن

إذا قام مصمم الفئة بإرجاع قيمة ، فسيتم استبدال إعلان الفئة بوظيفة المُنشئ المقدمة.

أيضًا ، يبدو أن المثال التالي من المستندات يشير إلى أن نوع المثيل Greeter سيكون له الخاصية newProperty ، وهذا ليس صحيحًا:

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

أعتقد أنه من المفيد أن نضيف إلى المستندات أن واجهة الفصل الذي تم إرجاعه لن تحل محل الواجهة الأصلية.

مثير للاهتمام ، لقد اختبرت مجموعة من الإصدارات القديمة من TypeScript ولم أتمكن من العثور على مناسبة حيث عملت عينة الكود هذه ( مثال من 3.3.3 ) - لذا نعم ، أوافق.

لقد جعلت https://github.com/microsoft/TypeScript-Website/issues/443 - إذا كان شخص ما يعرف كيفية جعل هذا classDecorator له نوع تقاطع صريح ، فالرجاء التعليق في هذه المشكلة

انتهى بي الأمر هنا لأن مشكلة الوثائق المذكورة أعلاه سببت لي الارتباك. في حين أنه سيكون من الرائع أن يتم التعرف على توقيع النوع للقيمة المرتجعة من المصمم بواسطة مترجم TS ، بدلاً من هذا التغيير الأكبر ، أوافق على أنه يجب توضيح الوثائق.

للتسجيل ، هذه نسخة مبسطة مما كنت أحاوله بتنسيق المستندات ذات الصلة من العلاقات العامة orta :

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);
هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات