Typescript: اقتراح: إضافة طرق ثابتة مجردة في فئات وطرق ثابتة في واجهات

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

كإحدى المشكلات المستمرة # 2947 ، والتي تسمح للمُعدِّل abstract في تعريفات الطريقة ولكنه لا يسمح به في إعلانات الطرق الثابتة ، أقترح توسيع هذه الوظيفة إلى إعلانات الطرق الثابتة من خلال السماح باستخدام معدِّل abstract static في تعريفات الطريقة .

المشكلة ذات الصلة تتعلق بالمعدِّل static على إعلان طرق الواجهة ، وهو غير مسموح به.

1. المشكلة

1.1 الأساليب المجردة الساكنة في الفصول المجردة

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

على سبيل المثال (مثال 1):

abstract class AbstractParentClass {
}

class FirstChildClass extends AbstractParentClass {

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

class SecondChildClass extends AbstractParentClass {

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

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

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

على سبيل المثال (المثال 2):

abstract class AbstractParentClass {
}

class FirstChildClass extends AbstractParentClass {

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

class SecondChildClass extends AbstractParentClass {

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

abstract class AbstractParentClassFactory {

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

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

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

نتيجة لذلك ، يقرر المترجم حدوث خطأ ويعرض الرسالة: الخاصية 'getSomeClassDependentValue' غير موجودة في النوع 'typeof AbstractParentClass'.

1.2 الأساليب الثابتة في الواجهات

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

على سبيل المثال (المثال 3):

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

عند تجميع هذه التعليمات البرمجية ، يحدث خطأ: لا يمكن أن يظهر المعدل "الثابت" على عضو من النوع.

2. الحل

الحل لكلا المشكلتين (1.1 و 1.2) هو السماح للمُعدِّل abstract بإعلانات الطريقة الثابتة في الفئات المجردة والمُعدِّل static في الواجهات.

3. تنفيذ JS

يجب أن يكون تنفيذ هذه الميزة في JavaScript مشابهًا لتنفيذ الواجهات والطرق المجردة والأساليب الثابتة.

هذا يعني ذاك:

  1. يجب ألا يؤثر إعلان الطرق الثابتة المجردة في فئة مجردة على تمثيل فئة الملخص في كود JavaScript.
  2. يجب ألا يؤثر إعلان الطرق الثابتة في الواجهة على تمثيل الواجهة في كود JavaScript (غير موجود).

على سبيل المثال ، كود TypeScript (المثال 4):

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

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

class FirstChildClass extends AbstractParentClass {

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

class SecondChildClass extends AbstractParentClass implements Serializable {

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

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

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

يجب تجميعها في كود JS هذا:

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

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

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

4. النقاط ذات الصلة

  • يجب وضع علامة على التصريح عن طريقة مجردة ثابتة لفئة مجردة باستخدام معدل abstract static
  • يجب وضع علامة على تنفيذ الطريقة الثابتة المجردة لفئة مجردة باستخدام معدل abstract static أو static
  • إعلان الأسلوب الثابت للواجهة يجب أن يتم تمييزه باستخدام معدل static
  • يجب أن يتم تمييز تنفيذ الطريقة الثابتة للواجهة باستخدام معدل static

يجب أن يتم توريث جميع الخصائص الأخرى لمعدِّل abstract static من خصائص معدِّلات abstract و static .

يجب أن يتم توريث كافة الخصائص الأخرى لمعدِّل أسلوب الواجهة static من طرق الواجهة وخصائص المُعدِّل static .

5. قائمة مراجعة ميزة اللغة

  • نحوي

    • _ ما هي القواعد النحوية لهذه الميزة؟ _ - القواعد النحوية لهذه الميزة هي معدّل abstract static لطريقة فئة مجردة و static معدّل لطريقة واجهة.

    • _هل هناك أي آثار على توافق جافا سكريبت الخلفي؟ إذا كان الأمر كذلك ، فهل تم تخفيفها بشكل كافٍ؟ _ - لا توجد أي آثار على توافق JavaScript الخلفي.

    • _ هل يتعارض بناء الجملة هذا مع ES6 أو تغييرات ES7 المعقولة؟ _ - لا يتداخل هذا النحو مع ES6 أو التغييرات ES7 المعقولة.

  • متعلق بدلالات الألفاظ

    • _ ما هو الخطأ في الميزة المقترحة؟ _ - الآن هناك أخطاء في تجميع معدّل abstract static لطريقة فئة مجردة ومعدل static لطريقة واجهة.

    • _كيف تؤثر الميزة على النوع الفرعي والنوع الفائق والهوية وعلاقات التخصيص؟ _ - لا تؤثر الميزة على علاقات النوع الفرعي والنوع الفائق والهوية وإمكانية التخصيص.

    • _كيف تتفاعل الميزة مع الأدوية؟ _ - لا تتفاعل الميزة مع الأدوية الجنيسة.

  • ينبعث

    • _ما هي تأثيرات هذه الميزة على إصدار JavaScript؟ _ - لا توجد تأثيرات لهذه الميزة على إصدار JavaScript.

    • _هل ينبعث هذا بشكل صحيح في وجود متغيرات من النوع "أي"؟ _ - نعم.

    • _ما هي التأثيرات على ملف التصريح (.d.ts) الصادرة؟ _ - لا توجد تأثيرات لملف التصريح.

    • _هل تعمل هذه الميزة بشكل جيد مع الوحدات الخارجية؟ _ - نعم.

  • التوافق

    • _هل هذا تغيير فاصل من المترجم 1.0؟ _ - ربما نعم ، لن يتمكن مترجم 1.0 من تجميع الكود الذي يطبق هذه الميزة.

    • _ هل هذا تغيير جذري عن سلوك JavaScript؟ _ - لا.

    • _هل هذا تنفيذ غير متوافق لميزة JavaScript مستقبلية (مثل ES6 / ES7 / أحدث)؟ _ - لا.

  • آخر

    • _هل يمكن تنفيذ الميزة دون التأثير سلبًا على أداء المترجم؟ _ - نعم على الأرجح.

    • _ما هو تأثيرها على سيناريوهات الأدوات ، مثل إكمال الأعضاء ومساعدة التوقيع في المحررين؟ _ - ربما ليس لها أي تأثير من هذا النوع.

Awaiting More Feedback Suggestion

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

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

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

ال 98 كومينتر

طرق الواجهة الثابتة بشكل عام غير منطقية ، راجع # 13462

تأجيل هذا حتى نسمع المزيد من ردود الفعل عليه.

كان السؤال الرئيسي الذي طرحناه عند التفكير في هذا: من يُسمح له باستدعاء طريقة abstract static ؟ من المفترض أنه لا يمكنك استدعاء AbstractParentClass.getSomeClassDependentValue مباشرة. لكن هل يمكنك استدعاء الطريقة على تعبير من النوع AbstractParentClass ؟ إذا كان الأمر كذلك ، فلماذا يجب السماح بذلك؟ إذا لم يكن كذلك ، فما فائدة الميزة؟

طرق الواجهة الثابتة بشكل عام غير منطقية ، راجع # 13462

في مناقشة # 13462 ، لم أفهم لماذا لا معنى لطرق الواجهة static . لقد رأيت فقط أنه يمكن تنفيذ وظائفهم بوسائل أخرى (مما يثبت أنهم ليسوا بلا معنى).

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

لذلك لا أرى أي أسباب منطقية لوجود طريقة class static و عدم استخدام interface .

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

كان السؤال الرئيسي الذي طرحناه عند التفكير في هذا: من يُسمح له باستدعاء طريقة abstract static ؟ من المفترض أنه لا يمكنك استدعاء AbstractParentClass.getSomeClassDependentValue مباشرة. لكن هل يمكنك استدعاء الطريقة على تعبير من النوع AbstractParentClass ؟ إذا كان الأمر كذلك ، فلماذا يجب السماح بذلك؟ إذا لم يكن كذلك ، فما فائدة الميزة؟

سؤال جيد. نظرًا لأنك كنت تفكر في هذه المشكلة ، هل يمكنك مشاركة أفكارك حول هذا الموضوع؟

يتبادر إلى ذهني فقط أنه على مستوى المترجم لن يتم تعقب حالة المكالمة المباشرة AbstractParentClass.getSomeClassDependentValue (لأنه لا يمكن تتبعها) ، وسيحدث خطأ وقت تشغيل JS. لكنني لست متأكدًا مما إذا كان هذا يتوافق مع أيديولوجية TypeScript.

لقد رأيت فقط أنه يمكن تنفيذ وظائفهم بوسائل أخرى (مما يثبت أنهم ليسوا بلا معنى).

فقط لأن شيئًا ما قابل للتنفيذ ، لا يعني أنه منطقي. 😉

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

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

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

ماذا قال @ patryk-zielinski93. قادمًا من بضع سنوات من المشاريع في PHP التي نقوم بتحويلها إلى TS ، فإننا نريد طرقًا ثابتة في الواجهات.

من حالات الاستخدام الشائعة جدًا التي سيساعدها ذلك مكونات React ، وهي فئات ذات خصائص ثابتة مثل displayName و propTypes و defaultProps .

بسبب هذا القيد ، تشتمل كتابة React حاليًا على نوعين: فئة Component ComponentClass وواجهة $ # $ 4 $ # $ تتضمن وظيفة المُنشئ والخصائص الثابتة.

للكتابة بشكل كامل ، تحقق من مكون React بكل خصائصه الثابتة ، يحتوي أحدهما على نوعين يستخدم كلا النوعين.

مثال بدون ComponentClass : يتم تجاهل الخصائص الثابتة

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

type Props = { name: string };

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

مثال مع ComponentClass : يتم فحص نوع الخصائص الثابتة

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

أظن أن العديد من الأشخاص لا يستخدمون حاليًا ComponentClass ، غير مدركين أن خصائصهم الثابتة لا يتم فحصها من النوع.

المشكلة ذات الصلة: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/16967

هل هناك أي تقدم في هذا؟ أو أي حل بديل للمُنشئين في الفصول المجردة؟
هذا مثال:

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

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

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

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

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

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

useModel(Animal); // Works!

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

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

جديد في Typescript ، لكنني مندهش أيضًا أن هذا غير مسموح به افتراضيًا ، وأن الكثير من الأشخاص يحاولون تبرير عدم إضافة الميزة بأيٍّ من:

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

حالة استخدام بسيطة أخرى: (الطريقة المثالية ، والتي لا تعمل)

import {map} from 'lodash';

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

  items: any[];

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

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

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

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

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

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

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

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

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

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

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

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

آمل أن يفسر هذا متطلباتي. شكرا.

لأي شخص يبحث عن حل بديل ، يمكنك استخدام هذا المصمم:

class myClass {
    public classProp: string;
}

interface myConstructor {
    new(): myClass;

    public readonly staticProp: string;
}

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

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

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

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

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

5 سنتات هنا ، من وجهة نظر المستخدم هي:

بالنسبة لحالة الواجهات ، يجب السماح لها بالمعدِّل _static_

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

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

في رأيي أن هذا شعور خاطئ ومعيق.

هل هناك أي خلفية تقنية تجعل ميزة هذه اللغة صعبة أو مستحيلة التنفيذ؟

في صحتك

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

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

في حالات الفئات ، لا توجد طرق ثابتة في المثيل.

يمكنك وضع على ذلك؟ هذا ليس C #

هم موجودون فقط في class / constructor function ، لذلك يجب أن يتم وصفهم فقط في تلك الواجهة.

أننا حصلنا عليه وهذا ما نريد تغييره.

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

أنا أتفق تماما على ذلك. انظر مثال واجهة Json

مرحبًا kitsonk ، من فضلك ، هل يمكنك توضيح المزيد حول:

الفصول لها واجهتان وعقدان للتنفيذ ولا مفر من ذلك.

لم أفهم هذا الجزء.

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

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

في حالات الفئات ، لا توجد طرق ثابتة في المثيل. هم موجودون فقط في class / constructor function ، لذلك يجب أن يتم وصفهم فقط في تلك الواجهة.

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

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

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

أعتقد أن السماح لمُعدِّل static سيسمح لي بعمل شيء مثل: SalesRepresentativeInterface.staticMethodCall()

هل انا مخطئ

في صحتك

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

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

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

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

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

لسوء الحظ ، لكي يعمل هذا ، نحتاج إلى ميزتين من TypeScript: (1) طرق ثابتة في الواجهات وطرق ثابتة مجردة ؛ (2) القدرة على استخدام typeof كنوع تلميح مع الفئات العامة.

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

لم أفهم هذا الجزء.

الفصول لها واجهتان. دالة المُنشئ ونموذج المثيل الأولي. الكلمة الرئيسية class هي في الأساس سكر لذلك.

interface Foo {
  bar(): void;
}

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

declare const Foo: FooConstructor;

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

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

يمكنك وضع على ذلك؟ هذا ليس C #

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

const foo = new Foo();

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

لا يوجد .baz() في حالات Foo . .baz() موجود فقط في المنشئ.

من منظور الكتابة ، يمكنك الرجوع إلى / استخراج هاتين الواجهتين. يشير Foo إلى واجهة المثيل العامة ويشير typeof Foo إلى واجهة المُنشئ العامة (والتي قد تتضمن الطرق الثابتة).

لمتابعة شرح kitsonk ، إليك المثال أعلاه المعاد كتابته لتحقيق ما تريد:

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

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

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

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

لاحظ هنا ما يلي:

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

mhegazy : كان عليك إزالة مكالمتي toJSON في واجهة JSONSerializable. على الرغم من أن دالة المثال البسيط الخاصة بي تستدعي $ makeInstance فقط fromJSON ، فمن الشائع جدًا أن ترغب في إنشاء كائن ، ثم استخدامه. لقد أزلت قدرتي على إجراء مكالمات بشأن ما تم إرجاعه makeInstance ، لأنني لا أعرف في الواقع ما هو T داخل makeInstance (مناسب بشكل خاص إذا makeInstance هي طريقة فئة تُستخدم داخليًا لإنشاء مثيل ، ثم استخدمها). بالطبع يمكنني القيام بذلك:

interface JSONSerializer {
  toJSON(): any;
}

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

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

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

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

والآن أعلم أن T الخاص بي سيكون به جميع الطرق المتاحة على JSONSerializer . لكن هذا مطول جدًا ويصعب تفسيره (انتظر ، أنت تتجاوز ImplementsJSONSerializer ، لكن هذا ليس JSONSerializable ، أليس كذلك؟ انتظر ، أنت تكتب بطة ؟؟) . أسهل تفسير للإصدار هو:

interface JSONSerializer {
  toJSON(): any;
}

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

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

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

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

}

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

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

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

بمعنى آخر ، يمكن أن يكون لديك فئة واحدة بها fromJSON ثابتة ولها مثيلها toJSON :

interface JSONSerializer {
    toJSON(): any;
}

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

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

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


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

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

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

لكن ، من وجهة نظري ، أنظف وأسهل سبب حول طريقة تنفيذ هذا النمط الشائع بشكل استثنائي هو استخدام طرق ثابتة مجردة. أولئك منا الذين أحبوا هذه الميزة بلغات أخرى (Python ، PHP) يفقدون امتلاكها في TypeScript. شكرا على وقتك.

بعد ذلك أكدت أن الإصدار الذي يسهل تفسيره بشأن (فئات المصنع)

لست متأكدًا من أنني أوافق على أنه من الأسهل التفكير في ذلك.

لكن ، من وجهة نظري ، أنظف وأسهل سبب حول طريقة تنفيذ هذا النمط الشائع بشكل استثنائي هو استخدام طرق ثابتة مجردة. أولئك منا الذين أحبوا هذه الميزة بلغات أخرى (Python ، PHP) يفقدون امتلاكها في TypeScript.

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

@ kitsonk حق فاتني ذلك.

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

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

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

هنا:

export interface LocationModuleInterface {
  readonly name: string;

  getRecreationOptions(): ModuleRecreationOptions;
}

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

  abstract getRecreationOptions(): ModuleRecreationOptions;

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

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

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

أنا أحب TypeScript بشغف. كالمجنون أعني. ولكن هناك دائما لكن.

بدلاً من فرض تطبيق الأسلوب الثابت ، كان علي أن أتحقق مرة أخرى كما كنت أفعل كل شيء في JavaScript قديم عادي.

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

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

أو حتى أفضل:

export interface LocationModuleInterface {
  readonly name: string;

  getRecreationOptions(): ModuleRecreationOptions;
  dontForgetToHaveStaticMethodForRecreation();
}

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

@ malina-kirn

انظر المثال الأول لي عن "كتابة البط".

هذه المشكلة ليس لها أي تأثير على ما إذا كان يتم استخدام الكتابة بطة أم لا. TypeScript هو بطة مكتوبة.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

بناءً على رسالتي السابقة ، أشعر أن المُعدِّل static في الواجهات هو حل جيد حقًا لبعض حالات الاستخدام التي تحتاج إلى حل. أعتذر عن حجم هذا التعليق الكبير ولكن هناك الكثير الذي أريد توضيحه.

1) هناك أوقات نحتاج فيها إلى أن نكون قادرين على وصف شكل الجانب الثابت لكائن فئة بشكل صريح ، كما في المثال أعلاه.
2) هناك أوقات يكون فيها فحص الشكل في موقع التعريف أمرًا مرغوبًا فيه للغاية ، وأوقات مثل مثال OliverJAsh حيث يكون موقع الاستخدام في كود خارجي لن يتم التحقق منه وتحتاج إلى التحقق من الشكل في موقع التعريف .

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

تقترح المنشورات الأخرى #workarounds للتحقق من الشكل في موقع التعريف. بينما تسمح لك هذه الحلول المخيفة بالتحقق من الشكل في موقع التعريف (في بعض الحالات) ، فهناك مشاكل:

  • إنهم لا يحلون الرقم 1 أعلاه ، ما زلت بحاجة إلى الإعلان صراحة عن النوع في كل عضو في كل فصل.
  • في كثير من الحالات يفتقرون إلى الوضوح والأناقة وسهولة القراءة. إنها ليست سهلة الاستخدام دائمًا ، وليست بديهية ، ويمكن أن تستغرق بعض الوقت لمعرفة أنها ممكنة.
  • إنهم لا يعملون في جميع الحالات والعثور على #workaroundsToMakeTheWorkaroundsWork ليس ممتعًا ... أو منتجًا ... لكن في الغالب ليس ممتعًا.

فيما يلي مثال على الحل البديل الذي رأيته مؤخرًا لفرض فحص الشكل في موقع التعريف ...

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

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

حاول الآن استخدام هذا الحل البديل مع فصل دراسي مجردة

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

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

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

أعتقد أننا سنعمل على حل هذا أيضًا ... * تنهد *

...

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

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

ولكن انتظر هناك المزيد! دعونا نرى ماذا سيحدث إذا كنا نستخدم الأدوية ...

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

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

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

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


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

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

إذا كنت تستخدم موقعًا خارجيًا ، فأقنع المجتمع / المشرفين بقبول معدل static لأعضاء الواجهة. وفي غضون ذلك ، ستحتاج إلى حل بديل آخر. إنه ليس في موقع التعريف ولكن أعتقد أنه يمكنك استخدام نسخة معدلة من الحل البديل الموضح في # 8328 لتحقيق ذلك. شاهد الحل في التعليق من mhegazy في 16 مايو 2016 بإذن من RyanCavanaugh

سامحني إذا فاتني النقاط الرئيسية. إن فهمي للتردد في دعم static لأعضاء الواجهة هو في الأساس كره لاستخدام نفس الواجهة + الكلمات الرئيسية التي تنفذ لوصف كل من شكل المُنشئ وشكل المثيل.

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

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

هذا تذكير بالتبديل من Java إلى C# . عندما رأيت لأول مرة رمز C# يفعل هذا
C# var something = "something"; if(something == "something") { ... }
بدأت أجراس الإنذار تدق في رأسي ، وكان العالم ينتهي ، وكانوا بحاجة إلى استخدام "something".Equals(something) ، إلخ.

== للإشارة يساوي! لديك .Equals(...) منفصل لمقارنة السلسلة ...
ويجب أن تأتي السلسلة الحرفية أولاً حتى لا تحصل على استدعاء ref null. يساوي (...) على متغير فارغ ...
و ... و ... زيادة التنفس

وبعد أسبوعين من استخدام C# أدركت مدى سهولة استخدامه بسبب هذا السلوك. على الرغم من أن ذلك يعني التخلي عن التمييز الواضح بين الاثنين ، إلا أنه يُحدث تحسنًا كبيرًا في قابلية الاستخدام الأمر الذي يستحق كل هذا العناء.

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

سأقوم بتقييم وزني على abstract static في التعليق التالي ....

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

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

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

لذا abstract static ... لا يستحق كل هذا العناء. الكثير من المشاكل.

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

إذا كان موقع الاستخدام خارجيًا ، فإن ما تحتاجه حقًا هو أعضاء في الواجهة static

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

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

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

كنت أحاول استخدام الاستدلال بالكتابة كمثال على سبب أهمية الدعم المستقبلي.
بالرجوع إلى الواجهة هنا بشكل نحوي let something: ISomethingStatic = { isFlammable: 'Ys', isFlammable2: 'No' ... isFlammable100: 'No' } لا يتعين علينا الإعلان صراحة عن النوع كـ "نعم" | "لا" 100 مرة منفصلة.

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

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

على حساب بعض الإسهاب ، يمكنك تحقيق مستوى لائق من النوع الآمن:

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

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

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

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

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

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

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

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

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

RyanCavanaugh لا أعتقد أن أي شخص قد ذكر هذا بالضبط حتى الآن (على الرغم من أنني قد أفتقده في القراءة السريعة) ، ولكن ردًا على:

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

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

لذا فإن ما أقترحه هو إعطاء T.constructor النوع Function & {{ statics on T }} . سيسمح ذلك للفئات المجردة التي تعلن عن حقل abstract static foo بالوصول إليه بأمان عبر this.constructor.foo مع عدم التسبب في مشاكل مع عدم توافق المُنشئ.

وحتى إذا لم يتم تنفيذ إضافة الإحصائيات تلقائيًا إلى T.constructor ، فسنظل قادرين على استخدام خصائص abstract static في الفئة الأساسية عن طريق الإعلان يدويًا عن "constructor": typeof AbstractParentClass .

أعتقد أن الكثير من الناس يتوقعون أن يعمل المثال @ patryk-zielinski93. بدلاً من ذلك ، يجب أن نستخدم الحلول البديلة والمطولة والمبهمة. نظرًا لأن لدينا بالفعل فصول "تحوي بنية" وأعضاء ثابتون ، فلماذا لا يمكننا الحصول على مثل هذا السكر في نظام النوع؟

هنا ألمي:

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

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

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

ربما يمكننا بدلاً من ذلك / بالتوازي تقديم جملة static implements ؟ على سبيل المثال

interface Foo {
    bar(): number;
}

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

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

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

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

شخص واحد فقط (https://github.com/Microsoft/TypeScript/issues/14600#issuecomment-379645122) رد عليك RyanCavanaugh . أعدت كتابة كود OP لجعله أنظف قليلاً وأيضًا لتوضيح سؤالك وإجاباتي:

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

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

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

من الذي يجوز له استدعاء الأسلوب المجرد الثابت؟ من المفترض أنه لا يمكنك استدعاء Base.foo مباشرة

إذا كنت تقصد المرجع # 1 ، إذن نعم ، فلا يجب أن يكون قابلاً للاستدعاء أبدًا ، لأنه حسنًا هو مجرد ، ولا يحتوي حتى على جسم.
يُسمح لك فقط بالاتصال بالمرجع رقم 2.

لكن هل يمكنك استدعاء العملية على تعبير من النوع Base؟
إذا كان الأمر كذلك ، فلماذا يجب السماح بذلك؟

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

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

المرجع # 3 خطأ. لا ، لا يمكنك استدعاء "foo" هناك ، لأنه من المفترض أن يكون baz __instance__ لـ Child1 أو Child2 ، ولا تحتوي المثيلات على طرق ثابتة

المرجع # 4 (يجب أن يكون) صحيحًا. يمكنك (يجب أن تكون قادرًا على) استدعاء الأسلوب الثابت foo هناك ، لأنه من المفترض أن يكون baz مُنشئ Child1 أو Child2 ، وكلاهما يوسع Base ، وبالتالي يجب أن يكون قد نفذ foo ().

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

يمكنك تخيل هذا الموقف:

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

ما فائدة الميزة؟

انظر المرجع # 4 الاستخدام هو عندما لا نعرف أي فئة من الفئات الفرعية نتلقىها كوسيطة (قد تكون Child1 أو Child2) ، لكننا نريد أن نسميها طريقة ثابتة ونتأكد من وجود هذه الطريقة.
أنا أقوم بإعادة كتابة إطار عمل node.js المعروف باسم AdonisJS. لقد تمت كتابته بلغة JS نقية ، لذلك أقوم بتحويلها إلى TS. لا يمكنني تغيير طريقة عمل الكود ، فأنا أضيف أنواعًا فقط. قلة هذه الميزات تجعلني حزينا جدا.

ملاحظة في هذا التعليق ، ولأسباب تتعلق بالبساطة ، كتبت عن الفئات المجردة فقط ، ولم أذكر الواجهات. لكن كل ما كتبته ينطبق على الواجهات. أنت فقط تستبدل فئة الملخص بواجهة ، وسيكون كل شيء على ما يرام. هناك احتمال ، عندما لا يرغب فريق TS لسبب ما (لا أعرف السبب) في تنفيذ abstract static في abstract class ، لكن سيكون من الجيد تنفيذ static فقط كلمة في واجهات ، من شأنها أن تجعلنا سعداء بما فيه الكفاية ، على ما أعتقد.

pps لقد قمت بتحرير أسماء الفصل والطريقة في أسئلتك لتتوافق مع التعليمات البرمجية الخاصة بي.

زوجان من الأفكار ، بناءً على الحجج الرئيسية للمشككين الآخرين:

"لا ، يجب ألا يتم تنفيذ هذا ، ولكن يمكنك فعل ذلك بالفعل بهذه الطريقة * يكتب 15 مرة أكثر من سطور التعليمات البرمجية *".

هذا ما صنع السكر النحوي. دعونا نضيف القليل من (السكر) إلى TS. لكن لا ، من الأفضل أن نعذب عقول المطورين الذين يحاولون اختراق الديكور وعشرات من الأدوية الجنسية بدلاً من إضافة كلمة بسيطة واحدة static إلى الواجهات. لماذا لا نعتبر ذلك static سكرًا آخر لجعل حياتنا أسهل؟ بنفس الطريقة التي يضيف بها ES6 class (والذي أصبح في كل مكان في الوقت الحاضر). لكن لا ، دعونا نكون مهووسين ونفعل الأشياء القديمة والطريقة "الصحيحة".

"هناك نوعان من واجهات لفئات js: للمُنشئ والمثيل".

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

وبخصوص هذا:

تأجيل هذا حتى نسمع المزيد من ردود الفعل عليه.

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

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

الحل الممكن؟

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

declare global {
  interface Function extends StaticInterface {
  }
}

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

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

export class OtherClass {
  id = "Yaaa"
}

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

اقتراح: أعضاء ثابتة في واجهات وأنواع ، وأعضاء مجردة في فصول مجردة ، v2

استخدم حالات

الأنواع المتوافقة مع Fantasyland

مطبعية
يمتد تطبيق الواجهة تطبيق {ثابت من (أ: أ): تطبيقي ؛ }

const من = > (c: C، a: A): new C => c.of (a) ؛ ""

Serializable النوع مع طريقة deserialize ثابتة

مطبعية
واجهة Serializable {
إزالة التسلسل الثابت (s: string): Serializable؛

serialize(): string;

}
""

العقود بشكل عام

مطبعية
عقد الواجهة {
إنشاء ثابت (x: رقم): عقد ؛
static new (x: number): عقد ؛
}

const factory1 = (c: static Contract): Contract => c.create (Math.random ()) ؛
const factory2 = (C: static Contract): Contract => new C (Math.random ()) ؛
مصنع const3 =(c: C): new C => c.create (Math.random ()) ؛

const c1 = factory1 (ContractImpl) ؛ // Contract
const c2 = factory2 (ContractImpl) ؛ // Contract
const c3 = factory3 (ContractImpl) ؛ // ContractImpl
""

بناء الجملة

الأساليب والمجالات الثابتة

مطبعية
واجهة Serializable {
إزالة التسلسل الثابت (s: string): Serializable؛
}

اكتب Serializable = {
إزالة التسلسل الثابت (s: string): Serializable؛
} ؛

فئة مجردة Serializable {
إزالة تسلسل الملخص الثابت (s: string): Serializable؛
}
""

توقيعات المُنشئ الثابتة

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

المتابعة: تواقيع المكالمات الثابتة

يناقش:

كيف يمكن التعبير عن تواقيع المكالمات الثابتة؟
إذا أردنا التعبير عنها ببساطة بإضافة معدِّل static قبل توقيع المكالمة ،
كيف سنميزهم عن طريقة المثيل بالاسم 'static' ؟
هل يمكننا استخدام نفس الحل البديل للطرق المسماة 'new' ؟
في مثل هذه الحالة ، سيكون بالتأكيد تغييرًا جذريًا.

عامل التشغيل من النوع static

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

عامل التشغيل من النوع new

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

فتحات داخلية

الفتحة الداخلية [[Static]]

سيتم تخزين الواجهة "الثابتة" من نوع ما في الفتحة الداخلية [[Static]] :

مطبعية
// نوع
واجهة Serializable {
إزالة التسلسل الثابت (s: string): Serializable؛
التسلسل: سلسلة ؛
}

// سيتم تمثيلها داخليًا كـ
// واجهة Serializable {
// [[ثابتة]]: {
// [[مثيل]]: Serializable؛ // انظر أدناه.
// إلغاء التسلسل (s: string): Serializable؛
//} ؛
// تسلسل (): سلسلة ؛
//}
""

بشكل افتراضي ، اكتب never .

الفتحة الداخلية [[Instance]]

سيتم تخزين واجهة "مثيل" من النوع
في الفتحة الداخلية [[Instance]] # $ 8 $ # $ لنوع الفتحة الداخلية [[Static]] .

بشكل افتراضي ، اكتب never .

دلالات

بدون عامل التشغيل static

عند استخدام نوع كنوع قيمة ،
سيتم تجاهل الفتحة الداخلية [[Static]] :

مطبعية
يعلنون ثابتًا قابلاً للتسلسل: قابل للتسلسل ؛

اكتب T = typeof قابل للتسلسل ؛
// {serialize (): string؛ }
""

لكن النوع نفسه يظل يحتفظ بالفتحة الداخلية [[Static]] :

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

الاحالة

كلتا قيم الأنواع المدركة للثابت قابلة للتخصيص إلى متطابقة هيكليًا
(باستثناء [[Static]] بالطبع) والعكس صحيح.

عامل التشغيل static

| الترابطية | أسبقية |
| : -----------: | : --------------------------------: |
| حق | IDK ، ولكنه يساوي new 's one |

يُرجع عامل التشغيل static نوع الفتحة الداخلية [[Static]] من النوع.
يشبه إلى حد ما عامل التشغيل من النوع typeof ،
لكن يجب أن تكون الوسيطة نوعًا وليس قيمة.

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

عامل التشغيل من النوع typeof يتجاهل أيضًا الفتحة الداخلية [[Instance]] :

مطبعية
إعلان const SerializableImpl: ثابت Serializable ؛

اكتب T = typeof SerializableImpl ؛
// {إلغاء التسلسل (s: string): Serializable؛ }
""

الاحالة

كلتا قيم الأنواع المدركة للمثيل قابلة للتخصيص إلى متطابقة هيكليًا
(باستثناء الفتحة الداخلية [[Instance]] بالطبع) والعكس صحيح.

عامل التشغيل new

| الترابطية | أسبقية |
| : -----------: | : -----------------------------------: |
| حق | IDK ، ولكنه يساوي static الخاص بـ |

ترجع عوامل التشغيل new نوع الفتحة الداخلية [[Instance]] من النوع.
إنه يعكس بشكل فعال عامل التشغيل static :

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

extends / implements دلالات

فئة تنفذ واجهة بفتحة داخلية غير افتراضية [[Static]]
يجب أن يكون لديك فتحة داخلية متوافقة مع [[Static]] .
يجب أن تكون فحوصات التوافق هي نفسها (أو مشابهة لها)
فحوصات التوافق العادية (المثيل).

مطبعية
فئة SerializableImpl تنفذ Serializable {
فك تسلسل ثابت (سلسلة: سلسلة): SerializableImpl {
// يذهب منطق إزالة التسلسل هنا.
}

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

serialize(): string {
    //
}

}
""

لكى يفعل

  • [] حدد بناء الجملة الذي يجب استخدامه لتوقيعات المكالمة الثابتة.
    ربما دون تغيير التغييرات.
  • [] هل هناك حالات خاصة بأنواع شرطية وعامل تشغيل infer ؟
  • [] التغييرات في دلالات أعضاء الفصل غير المجردة. _قد يكون كسر ._

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

المشكلة التي لدي هي أنني أريد إجراء تعديل على فئة تم إنشاؤها من خلال التعليمات البرمجية. على سبيل المثال ، ما أود فعله هو:

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

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

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

export default Message;

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

يبدو هذا ملائمًا للنشر هنا كحالة استخدام لا يوجد حل بديل لها على ما يبدو وبالتأكيد لا يوجد حل بديل مباشر.

إذا كنت تريد التحقق من المثيل والجانب الثابت للفئة مقابل الواجهات ، فيمكنك القيام بذلك على النحو التالي:

interface C1Instance {
  // Instance properties ...

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

  // Prototype properties ...
}

interface C1Constructor {
  new (): C1Instance;

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

  // Static properties ...
}

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

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

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

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

الشيء مع أي لغة ، طبيعية وصناعية ، يجب أن تتطور بشكل طبيعي لتكون فعالة وجذابة للاستخدام. في النهاية قرر الناس استخدام التخفيضات في اللغة ، ("okay" => "ok" ، "go to" => "gonna") ، واخترعوا كلمات سخيفة جديدة ، مثل "selfie" و "google" ، وأعادوا تعريف التهجئة باستخدام l33tspeak و الأشياء ، وحتى حظر بعض الكلمات ، وعلى الرغم من رغبتك في استخدامها أم لا ، فلا يزال الجميع يفهم ما تعنيه ، والبعض منا يستخدمها لتحقيق بعض المهام المحددة مهما كانت. ولا يمكن أن يكون هناك سبب وجيه لأي من هؤلاء ، ولكن كفاءة بعض الأشخاص في مهام معينة ، الأمر كله يتعلق بعدد الأشخاص الذين يستفيدون منها بالفعل. حجم هذه المحادثة يظهر بوضوح ، أن الكثير من الناس يمكنهم الاستفادة من هذا static abstract لأي اعتبارات ملعون لديهم. لقد جئت إلى هنا للسبب نفسه ، لأنني أردت تنفيذ Serializable لذلك جربت جميع الطرق البديهية (بالنسبة لي) للقيام بذلك ، ولم يعمل أي منها. صدقني ، آخر شيء أود البحث عنه هو شرح سبب عدم حاجتي إلى هذه الميزة ويجب أن أختار شيئًا آخر. سنة ونصف ، يسوع المسيح! أراهن أن هناك علاقات عامة في مكان ما بالفعل ، مع الاختبارات والأشياء. من فضلك اجعل هذا يحدث ، وإذا كانت هناك طريقة معينة لاستخدام غير محبذة ، فلدينا tslint لذلك.

من الممكن الوصول إلى أعضاء ثابتة من الفئات الفرعية من الفئة الأساسية عبر this.constructor.staticMember ، لذا فإن الأعضاء الثابتون المجردون منطقيون بالنسبة لي.

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

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

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

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

أى اخبار ؟
الميزات مطلوبة حقًا 😄
1) وظائف static في الواجهات
2) abstract static وظائف في فصول مجردة

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

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

يمكن استخدامها على النحو التالي:

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

تحديث:

مزيد من التفاصيل حول الفكرة ومثال حي:

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

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

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

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

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

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

@ aleksey-bykov قد لا يكون هذا خطأ من شركة Typescript ، لكنني لم أستطع الحصول على مصممي مكون Angular العامل ومجمع AoT الخاص بهم.

@ aleksey-bykov هذا ذكي ولكنه لا يعمل مع الصور الثابتة المجردة. إذا كان لديك أي فئات فرعية من MyClass ، فلن يتم فرضها بالتحقق من النوع. إنه أسوأ أيضًا إذا كان لديك أدوية جنيسة.

// no errors
class Thing extends MyClass {

}

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

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

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

ضد

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

@ aleksey-bykov هناك الكثير من الأسباب. على سبيل المثال ( من @ patryk-zielinski93):

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

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

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

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

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

ما المشكلة؟

@ aleksey-bykov لا تبدو الدرجة المنفصلة جميلة

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

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

إضافة إلغاء التسلسل المعاكس إلى الفصل نفسه كما اقترحت يمنحه مسؤولية إضافية وسببًا إضافيًا للتغيير

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

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

باستثناء أنه يتعين عليك تغيير الكود في مكانين بدلاً من مكان واحد ، لأن التسلسل (de) يعتمد على هيكل النوع الخاص بك. هذا لا يقع ضمن "المسؤولية الفردية"

إضافة إلغاء التسلسل المعاكس إلى الفصل نفسه كما اقترحت يمنحه مسؤولية إضافية وسببًا إضافيًا للتغيير

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

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

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

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

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

يا رجل ، أي اقتراح جيد يتميز باستخدام حالات ، هذا ليس كذلك

image

لذلك أعربت عن بعض الفضول فقط وطرحت سؤالاً ، بما أن الاقتراح لا يقول ذلك ، فلماذا قد يحتاجه الناس

لقد اختفت إلى نموذجي: فقط لأنك لا تجرؤ

أنا شخصياً لا أهتم بالصلابة أو الخطأ ، لقد تصادف أن أفرط في النمو منذ وقت طويل ، لقد طرحته من خلال طرح حجة "uberclass antipattern" ثم نسخها احتياطيًا إلى "درجة من الحرية لتفسيرها"

السبب العملي الوحيد المذكور في هذه المناقشة بأكملها هو: https://github.com/Microsoft/TypeScript/issues/14600#issuecomment -308362119

من حالات الاستخدام الشائعة جدًا التي سيساعدها هذا الأمر مكونات React ، وهي فئات ذات خصائص ثابتة مثل displayName و propTypes و defaultProps.

وبعض المنشورات على حد سواء https://github.com/Microsoft/TypeScript/issues/14600#issuecomment -345496014

لكنها مغطاة بـ (new (...) => MyClass) & MyStaticInterface

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

من المنطقي أن الأشياء العملية هي تلك التي تحل المشاكل ، والأشياء غير العملية هي تلك التي تخلط إحساسك بالجمال ، وأنا أحترم الآخرين ولكن مع كل هذا الاحترام ، لا يزال السؤال (معظمه بلاغي الآن) قائما: ما المشكلة التي ينوي هذا الاقتراح حلها حل بالنظر (new (...) => MyClass) & MyStaticInterface لـ https://github.com/Microsoft/TypeScript/issues/14600#issuecomment -308362119 و https://github.com/Microsoft/TypeScript/issues/14600#issuecomment -289084844 سبب عادل

من فضلك لا تجيب

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

بالإضافة إلى ذلك ، ما زلنا لم نتناول الفئات المجردة؟

الحل في عدم استخدام الطبقات المجردة ليس هو الحل في رأيي. هذا هو الحل! عمل حول ماذا؟

أعتقد أن طلب الميزة هذا موجود لأن العديد من الأشخاص ، بما في ذلك مقدم الطلب ، وجدوا أن ميزة متوقعة مثل abstract static أو static في الواجهات لم تكن موجودة.

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

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

هل كان هناك أي تحديث لهذا الاقتراح؟ هل هناك أي حجج تستحق الدراسة توضح لماذا لا يجب أن يكون لدينا static abstract وما شابه ذلك؟
هل يمكننا الحصول على مزيد من الاقتراحات التي توضح سبب كونها مفيدة؟

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

هناك اقتراحان كما أفهمهما:

  1. يمكن للواجهات والأنواع تحديد الخصائص والأساليب الثابتة
interface ISerializable<T> { 
   static fromJson(json: string): T;
}
  1. يمكن للفئات المجردة تحديد الأساليب الثابتة المجردة
abstract class MyClass<T> implements ISerializable<T> {
   abstract static fromJson(json: string): T;
}

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

بالنسبة للاقتراح الأول ، هناك حل تقني! هذا ليس رائعًا ، لكن هذا شيء على الأقل. لكنها حل بديل.

يمكنك تقسيم واجهاتك إلى قسمين وإعادة كتابة فصلك مثل

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

interface InstanceInterface {
  toJson(): string;
}

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

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

ولكن بعد ذلك ، ماذا عن الاقتراح 2؟ لا يوجد شيء يمكن القيام به حيال ذلك ، أليس كذلك؟ أعتقد أن هذا يستحق المعالجة أيضًا!

ما هو الاستخدام العملي لأحد هذه الأنواع - كيف يمكن استخدام أحد هذه الأنواع؟

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

من الممكن بالفعل أن نقول أن القيمة يجب أن تكون كائنًا مثل { fromJSON(serializedValue: string): JsonSerializable; } ، فهل هذا مطلوب فقط لفرض نمط؟ لا أرى فائدة ذلك من منظور التحقق من النوع. كملاحظة جانبية: في هذه الحالة ، قد يتم فرض نمط يصعب التعامل معه - سيكون من الأفضل نقل عملية التسلسل إلى فئات أو وظائف متسلسلة منفصلة لأسباب عديدة لن أتطرق إليها هنا.

بالإضافة إلى ذلك ، لماذا يتم القيام بشيء مثل هذا؟

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

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

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

هناك حالة استخدام واحدة صالحة لطرق React الثابتة ، وهي حولها

@ aleksey-bykov آه ، حسنًا. بالنسبة لتلك الحالات ، قد يكون من الأفضل أن تتسبب إضافة نوع في الخاصية constructor في التحقق من النوع في هذا السيناريو النادر. علي سبيل المثال:

interface Component {
    constructor: ComponentConstructor;
}

interface ComponentConstructor {
    displayName?: string;
}

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

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


بالمناسبة ، كنت أفكر أن حالة الاستخدام الصالحة ستكون عند الانتقال من مثيل إلى المُنشئ وأخيراً إلى طريقة ثابتة أو خاصية ثابتة مع التحقق من النوع ، ولكن هذا ممكن بالفعل عن طريق كتابة الخاصية constructor على نوع وسيتم حلها لمثيلات الفصل في # 3841.

كانت لدي فكرة مماثلة: https://github.com/Microsoft/TypeScript/issues/14600#issuecomment -437071092

هل نمط التسلسل / إلغاء التسلسل الموصوف لأعلى غير "صالح"؟

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

لنفترض أن لدي مكتبة توفر مجموعة من فئات الأشقاء بطريقة foo(x: number, y: boolean, z: string) ثابتة ، والتوقع هو أن يكتب المستخدمون فئة مصنع تأخذ العديد من هذه الفئات وتستدعي الطريقة لإنشاء مثيلات. (ربما يكون deserialize أو clone أو unpack أو loadFromServer ، لا يهم). فئة من المكتبة.

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

أريد أن أضمن أن المستخدمين الذين يقومون بالتحديث إلى إصدار المكتبة الجديد سيلاحظون التغييرات العاجلة في وقت الترجمة. يتعين على المكتبة تصدير أحد الحلول البديلة للواجهة الثابتة أعلاه (مثل type MyClass = (new (data: number[]) => MyInterface) & MyStaticInterface; ) ونأمل أن يقوم المستهلك بتطبيقها في جميع الأماكن الصحيحة. إذا نسوا تزيين أحد تطبيقاتهم أو إذا لم يستخدموا نوع مكتبة مُصدَرة لوصف توقيع المكالمة foo في فئة المصنع الخاص بهم ، فلن يتمكن المترجم من معرفة أي شيء تغير وستحصل على أخطاء وقت التشغيل . على النقيض من ذلك مع التنفيذ المعقول لطرق abstract static في الفصل الرئيسي - لا توجد تعليقات توضيحية خاصة مطلوبة ، ولا عبء على الكود المستهلك ، إنه يعمل فقط خارج الصندوق.

@ thw0rted ألن تطلب معظم المكتبات نوعًا من التسجيل لهذه الفئات ويمكن التحقق من النوع في هذه المرحلة؟ علي سبيل المثال:

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

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

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

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

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

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

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

في مرحلة ما ، قمت بتغيير ذلك إلى طلب shortName و longName (للعرض في سياقات مختلفة بكميات مختلفة من مساحة الشاشة المتاحة). كان من الأسهل بكثير تغيير abstract static name: string; إلى abstract static shortName: string; إلخ ، بدلاً من تغيير مكون قائمة الاختيار ليكون providerList: Type<Provider> & { shortName: string } & { longName: string } . إنه ينقل النية ، في المكان المناسب (أي في الأصل المجرد ، وليس في المكون المستهلك) ، ويسهل قراءته وتغييره بسهولة. أعتقد أنه يمكننا القول إن هناك حلًا بديلًا ، لكنني ما زلت أعتقد أنه من الناحية الموضوعية أسوأ من التغييرات المقترحة.

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

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

شكرا على الأجوبة.

greeny هنا حل عملي: https://github.com/Microsoft/TypeScript/issues/14600#issuecomment -437071092

على وجه التحديد هذا الجزء منه:

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

حالة استخدام أخرى: كود مُنشأ / حشوات فئة جزئية

  • أنا أستخدم مكتبة @ rsuter / nswag ، التي تنشئ مواصفات swagger.
  • يمكنك كتابة ملف "امتدادات" يتم دمجه في الملف الذي تم إنشاؤه.
  • لا يتم تشغيل ملف الامتدادات أبدًا ، ولكنه يحتاج إلى التجميع من تلقاء نفسه!
  • أحتاج أحيانًا داخل هذا الملف للإشارة إلى شيء غير موجود بعد (لأنه تم إنشاؤه)
  • لذلك أريد أن أعلن عن shim / واجهة لها على النحو التالي
  • ملاحظة: يمكنك تحديد أن بعض عبارات import معينة يتم تجاهلها في الدمج النهائي لذلك يتم تجاهل تضمين "shim" في الكود النهائي الذي تم إنشاؤه.
interface SwaggerException
{
    static isSwaggerException(obj: any): obj is SwaggerException;
}

لكن لا يمكنني فعل ذلك ، ويجب أن أكتب فصلًا دراسيًا بالفعل - وهو أمر جيد ولكن لا يزال أشعر بالخطأ. أريد فقط - كما قال كثيرون - أن أقول "هذا هو العقد"

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

أود أيضًا أن أرى ملفات أكثر نظافة d.ts - يجب أن يكون هناك بعض التداخل مع هذه الميزة وتلك الملفات. من الصعب فهم شيء مثل JQueryStatic لأنه يبدو أنه مجرد اختراق. كما أن الحقيقة هي أن الملفات d.ts غالبًا ما تكون قديمة ولا يتم صيانتها وتحتاج إلى إعلان الحشوات بنفسك.

(آسف لذكر jQuery)

بالنسبة لحالة التسلسل ، فعلت شيئًا من هذا القبيل.

export abstract class World {

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

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

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

ولكن لا يزال من الأسهل استخدام شيء من هذا القبيل:

export abstract class World {

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

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

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

هذا العدد مفتوح لمدة عامين ولديه 79 تعليقًا. يُسمى Awaiting More Feedback . هل يمكنك أن تقول ما هي التعليقات الأخرى التي تحتاجها لاتخاذ قرار؟

إنه أمر مزعج حقًا أنني لا أستطيع وصف الطريقة الثابتة لا في الواجهة ولا في فئة مجردة (إعلان فقط). من القبيح جدًا كتابة الحلول لأن هذه الميزة لم يتم تنفيذها بعد = (

على سبيل المثال ، يستخدم next.js دالة getInitialProps ثابتة للحصول على خصائص الصفحة قبل إنشاء الصفحة. في حالة رميها ، لا يتم تكوين الصفحة ، بل يتم إنشاء صفحة الخطأ.

https://github.com/zeit/next.js/blob/master/packages/next/README.md#fetching- دورة حياة البيانات والمكونات

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

أعتقد أن هذه المشكلة موجودة هنا منذ وقت طويل لأن JavaScript نفسها ليست جيدة في الشيء الثابت 🤔

يجب ألا يوجد الميراث الثابت في المقام الأول. 🤔🤔

من يريد استدعاء طريقة ثابتة أو قراءة حقل ثابت؟ 🤔🤔🤔

  • فئة فرعية: يجب ألا تكون ثابتة
  • فئة الأصل: استخدم وسيطة منشئ
  • آخر: استخدم واجهة ISomeClassConstructor

هل هناك أي حالة استخدام أخرى؟

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

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

يعرف المصمم بأنه:

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

إذا كان لديّ واجهة عضو مُنشئ ثابتة مُعرَّفة على أنها

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

واستدعيت في الفصل الذي يجب أن يعلن بشكل ثابت عن الأعضاء في T على النحو التالي:

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

المثال الأكثر شيوعًا هو جيد:

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

لكن كما قيل في # 13462 هنا :

يجب أن تحدد الواجهات الوظيفة التي يوفرها الكائن. يجب أن تكون هذه الوظيفة قابلة للتجاوز وقابلة للتبديل (لهذا السبب تكون طرق الواجهة افتراضية). الإحصائيات هي مفهوم مواز للسلوك الديناميكي / الأساليب الافتراضية.

أوافق على النقطة التي مفادها أن الواجهات ، في TypeScript ، تصف فقط مثيل الكائن نفسه ، وكيفية استخدامه. تكمن المشكلة في أن مثيل الكائن ليس تعريف فئة ، وقد يوجد رمز static فقط في تعريف فئة .

لذلك يمكنني أن أقترح ما يلي بكل عيوبه:


يمكن أن تكون الواجهة إما تصف كائنًا أو فئة . لنفترض أن واجهة الفصل تمت ملاحظتها بالكلمات الرئيسية class_interface .

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

يمكن للفئات (وواجهات الفئة) استخدام الكلمات الرئيسية statically implements للإعلان عن رموزها الثابتة باستخدام واجهة كائن (لا يمكن تنفيذ واجهات الفئة statically ).

لا تزال الفئات (وواجهات الفئة) تستخدم الكلمة الأساسية implements مع واجهة كائن أو واجهة فئة .

يمكن لواجهة الفئة بعد ذلك المزج بين واجهة كائن مطبقة بشكل ثابت وواجهة تم تنفيذها على سبيل المثال. وبالتالي ، يمكننا الحصول على ما يلي:

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

class_interface ISerDes implements ISerializable statically implements IDeserializable {}

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

حالة استخدام صغيرة واحدة غير حرجة أكثر:
في Angular لتجميع AOT ، لا يمكنك استدعاء وظائف في الديكور (مثل @NgModule module decorator)
قضية الزاوي

بالنسبة لوحدة عامل الخدمة ، فأنت بحاجة إلى شيء مثل هذا:
ServiceWorkerModule.register('ngsw-worker.js', {enabled: environment.production})
تستخدم بيئتنا الفئات الفرعية ، لتوسيع فئة الملخص بقيم افتراضية وخصائص مجردة للتنفيذ. مثال تنفيذ مماثل
لذلك لا تعمل AOT لأن إنشاء فئة هي دالة ، وتلقي خطأ مثل: Function calls are not supported in decorators but ..

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

مع واجهة يمكن أن تعمل بهذه الطريقة:

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

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

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

export const environment = Env;

باستخدام الإحصائيات المجردة يمكن أن تعمل بهذه الطريقة:

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

export const environment = Env;

هناك حلول ، لكنها كلها ضعيفة: /

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

أليست هذه المشكلة نسخة مكررة من # 1263؟ 😛

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

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

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

مثال:

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

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

الآن بدافع الضرورة أستخدم هذا

abstract class MultiWalletInterface {

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

  static deleteInstance() {}

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

هذا غير مريح!

لقد جئت مع مشكلة حيث أقوم بإضافة خصائص إلى "الكائن" ، وهنا مثال وضع الحماية

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

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

يتم اعتبار أي مثيل كائن الآن على أنه يحتوي على الخاصية getInstanceId بينما يجب أن يكون Object فقط. مع خاصية ثابتة ، كان من الممكن حل المشكلة.

تريد زيادة ObjectConstructor وليس Object. أنت تعلن عن طريقة مثيل ، عندما تريد فعلاً إرفاق طريقة بالمنشئ نفسه. أعتقد أن هذا ممكن بالفعل من خلال دمج الإعلان:

""
تعلن عالمية {
واجهة ObjectConstructor {
مرحبًا (): سلسلة ؛
}
}

Object.hello () ؛
""

@ thw0rted ممتاز! شكرا لم أكن على علم ببرنامج ObjectConstructor

النقطة الأكبر هي أنك تقوم بزيادة نوع المُنشئ بدلاً من نوع المثيل. لقد بحثت للتو عن إعلان Object في lib.es5.d.ts ووجدت أنه من النوع ObjectConstructor .

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

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

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

شيء من هذا القبيل:

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

interface Serializable {
    toJson(): string;
}

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

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

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

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

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

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

بصراحة ، يبدو أن الجزء الأصعب من هذا الأسلوب هو التوصل إلى كلمة رئيسية ذات مغزى ، TypeScript-y لـ metaclass ... staticimplements ، classimplements ، withstatic ، implementsstatic ... لست متأكدًا.

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

دعنا نلتقط المناقشة في # 34516 و # 33892 اعتمادًا على الميزة التي تريدها

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات