Typescript: تمديد تعدادات سلسلة

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

قبل التعداد القائم على السلسلة ، كان العديد من الأشخاص يعودون إلى الأشياء. يسمح استخدام الكائنات أيضًا بتوسيع الأنواع. علي سبيل المثال:

const BasicEvents = {
    Start: "Start",
    Finish: "Finish"
};

const AdvEvents = {
    ...BasicEvents,
    Pause: "Pause",
    Resume: "Resume"
};

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

سأكون مفيدًا جدًا لأكون قادرًا على القيام بشيء مثل هذا:

enum BasicEvents {
    Start = "Start",
    Finish = "Finish"
};

// extend enum using "extends" keyword
enum AdvEvents extends BasicEvents {
    Pause = "Pause",
    Resume = "Resume"
};

بالنظر إلى أن التعداد الناتج عبارة عن أشياء ، فلن يكون ذلك فظيعًا أيضًا:

// extend enum using spread
enum AdvEvents {
    ...BasicEvents,
    Pause = "Pause",
    Resume = "Resume"
};
Awaiting More Feedback Suggestion

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

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

ال 68 كومينتر

لقد لعبت بها قليلاً ومن الممكن حاليًا القيام بهذا الامتداد باستخدام كائن للنوع الممتد ، لذلك يجب أن يعمل هذا بشكل جيد:

enum BasicEvents {
    Start = "Start",
    Finish = "Finish"
};

// extend enum using "extends" keyword
const AdvEvents = {
    ...BasicEvents,
    Pause: "Pause",
    Resume: "Resume"
};

ملاحظة ، يمكنك الاقتراب من

enum E {}

enum BasicEvents {
  Start = 'Start',
  Finish = 'Finish'
}
enum AdvEvents {
  Pause = 'Pause',
  Resume = 'Resume'
}
function enumerate<T1 extends typeof E, T2 extends typeof E>(e1: T1, e2: T2) {
  enum Events {
    Restart = 'Restart'
  }
  return Events as typeof Events & T1 & T2;
}

const e = enumerate(BasicEvents, AdvEvents);

هناك خيار آخر ، بناءً على احتياجاتك ، وهو استخدام نوع الاتحاد:

const enum BasicEvents {
  Start = 'Start',
  Finish = 'Finish'
}
const enum AdvEvents {
  Pause = 'Pause',
  Resume = 'Resume'
}
type Events = BasicEvents | AdvEvents;

let e: Events = AdvEvents.Pause;

الجانب السلبي هو أنه لا يمكنك استخدام Events.Pause ؛ يجب عليك استخدام AdvEvents.Pause . إذا كنت تستخدم التعدادات الثابتة ، فمن المحتمل أن يكون هذا جيدًا. وإلا فقد لا يكون ذلك كافيًا لحالة الاستخدام الخاصة بك.

نحتاج إلى هذه الميزة لمخفضات Redux المكتوبة بقوة. الرجاء إضافته في TypeScript.

حل آخر هو عدم استخدام التعداد ، ولكن استخدام شيء يشبه التعداد:

const BasicEvents = {
  Start: 'Start' as 'Start',
  Finish: 'Finish' as 'Finish'
};
type BasicEvents = (typeof BasicEvents)[keyof typeof BasicEvents];

const AdvEvents = {
  ...BasicEvents,
  Pause: 'Pause' as 'Pause',
  Resume: 'Resume' as 'Resume'
};
type AdvEvents = (typeof AdvEvents)[keyof typeof AdvEvents];

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

فقط استخدم الفصل بدلاً من التعداد.

كنت فقط أحاول ذلك.

const BasicEvents = {
    Start: 'Start' as 'Start',
    Finish: 'Finish' as 'Finish'
};

type BasicEvents = (typeof BasicEvents)[keyof typeof BasicEvents];

const AdvEvents = {
    ...BasicEvents,
    Pause: 'Pause' as 'Pause',
    Resume: 'Resume' as 'Resume'
};

type AdvEvents = (typeof AdvEvents)[keyof typeof AdvEvents];

type sometype<T extends AdvEvents> =
    T extends typeof AdvEvents.Start ? 'Some String' :
    T extends typeof AdvEvents.Finish ? 'Some Other String' :
    T extends typeof AdvEvents.Pause ? 'Abc' :
    T extends typeof AdvEvents.Resume ? 'Xyz' : never;
type r = sometype<typeof AdvEvents.Finish>;

يجب أن يكون هناك طريقة أفضل للقيام بذلك.

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

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

import { Foo as _Foo } from './Foo';

namespace Bar
{
    enum Foo extends _Foo {} // nope, doesn't work

    const Foo = _Foo;
    type Foo = _Foo;
}

Bar.Foo // actually not an enum

obrazek

+1
يتم استخدام حل بديل حاليًا ، ولكن يجب أن يكون ميزة تعداد أصلية.

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

من OP:

enum BasicEvents {
    Start = "Start",
    Finish = "Finish"
};

// extend enum using "extends" keyword
enum AdvEvents extends BasicEvents {
    Pause = "Pause",
    Resume = "Resume"
};

هل يتوقع الناس AdvEvents إلى BasicEvents ؟ (كما هو الحال ، على سبيل المثال ، الحالة مع extends للفصول.)

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

masak نقطة كبيرة. الميزة التي يريدها الناس هنا ليست بالتأكيد مثل العادي extends . يجب BasicEvents لـ AdvEvents ، وليس العكس. عادي extends ينقح نوعًا آخر ليكون أكثر تحديدًا ، وفي هذه الحالة نريد توسيع النوع الآخر لإضافة المزيد من القيم ، لذلك يجب ألا يستخدم أي بناء جملة مخصص لهذا النوع extends ، أو على الأقل عدم استخدام الصيغة enum A extends B { .

في هذه الملاحظة ، أحببت اقتراح انتشار لهذا من OP.

// extend enum using spread
enum AdvEvents {
    ...BasicEvents,
    Pause = "Pause",
    Resume = "Resume"
};

لأن الانتشار يحمل بالفعل توقع كون الأصل مستنسخًا ضحلًا في نسخة غير متصلة.

يجب BasicEvents لـ AdvEvents ، وليس العكس.

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

فكرت في الحلول البديلة أكثر من ذلك بقليل ، والعمل على https://github.com/Microsoft/TypeScript/issues/17592#issuecomment -331491147 ، يمكنك القيام بعمل أفضل قليلاً من خلال تحديد Events في القيمة الفضاء:

enum BasicEvents {
  Start = 'Start',
  Finish = 'Finish'
}
enum AdvEvents {
  Pause = 'Pause',
  Resume = 'Resume'
}
type Events = BasicEvents | AdvEvents;
const Events = {...BasicEvents, ...AdvEvents};

let e: Events = Events.Pause;

من الاختبار الذي أجريته ، يبدو أن Events.Start قد تم تفسيره بشكل صحيح على أنه BasicEvents.Start في نظام النوع ، لذا يبدو أن فحص الشمولية والتحسين النقابي المميز يعملان بشكل جيد. الشيء الرئيسي المفقود هو أنه لا يمكنك استخدام Events.Pause كنوع حرفي ؛ تحتاج AdvEvents.Pause . يمكنك استخدام typeof Events.Pause ويتحول إلى AdvEvents.Pause ، على الرغم من أن الأشخاص في فريقي قد ارتبكوا بهذا النوع من النمط وأعتقد عمليًا أنني سأشجع AdvEvents.Pause عند الاستخدام كنوع.

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

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

type BEs = "Start" | "Finish";

type AEs = BEs | "Pause" | "Resume";

let example: AEs = "Finish"; // there is even autocompletion

لذا ، يمكن أن يكون حل مشاكلنا هذا؟

const BasicEvents = {
    Start: "Start",
    Finish: "Finish"
};
// { Start: string, Finish: string };


const BasicEvents = {
    Start: "Start",
    Finish: "Finish"
} as const
// { Start: 'Start', Finish: 'Finish' };

https://github.com/Microsoft/TypeScript/pull/29510

يجب أن يكون تمديد التعداد ميزة أساسية في TypeScript. مجرد قول'

wottpal أكرر سؤالي السابق:

إذا [يمكن تمديد التعدادات] ، فما مدى توافق ذلك مع حقيقة أن أنواع التعداد يجب أن تكون نهائية وليس من الممكن تمديدها؟

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

masak ماذا؟ لا ، لا! نظرًا لأن التعداد الموسع هو نوع أوسع ولا يمكن تعيينه إلى التعداد الأصلي ، فأنت تعرف دائمًا جميع قيم كل تعداد تستخدمه. التوسيع في هذا السياق يعني إنشاء تعداد جديد ، وليس تعديل التعداد القديم.

enum A { a; }
enum B extends A { b; }

declare var a: A;
switch(a) {
    case A.a:
        break;
    default:
        // a is never
}

declare var b: B;
switch(b) {
    case A.a:
        break;
    default:
        // b is B.b
}

@ m93a آه ، فأنت تعني أن extends هنا ساريًا يحتوي على المزيد من دلالات _copying_ (لقيم التعداد من A إلى B )؟ ثم ، نعم ، تظهر المفاتيح بشكل جيد.

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

لهذا السبب ، إذا كان class B extends A ، فإننا نضمن أن B يمكن تخصيصه لـ A ، وبالتالي على سبيل المثال let a: A = new B(); سيكون جيدًا تمامًا.

ولكن باستخدام التعدادات و extends ، لن نتمكن من القيام بـ let a: A = B.b; ، لأنه لا يوجد مثل هذا الضمان المقابل. وهو ما أشعر به غريبًا بالنسبة لي ؛ ينقل extends هنا مجموعة معينة من الافتراضات حول ما يمكن القيام به ، ولا يتم استيفائها بالتعدادات.

ثم مجرد تسميته expands أو clones ؟ 🤷‍♂️
من وجهة نظر المستخدمين ، يبدو الأمر غريبًا أن شيئًا أساسيًا ليس من السهل تحقيقه.

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

+1 آمل أن يتم إضافة هذا إلى ميزة التعداد الافتراضية. :)

هل يعرف أحد أي حلول أنيقة ؟؟؟ 🧐

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

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

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

// extend enum using spread
enum AdvancedEvents {
    ...BasicEvents,
    Pause = "Pause",
    Resume = "Resume"
};

هل لا تزال هذه المشكلة بحاجة فعلاً إلى التصنيف "في انتظار المزيد من التعليقات" في هذه المرحلة ، RyanCavanaugh ؟

أرادت الميزة +1

هل لدينا أي أخبار عن هذا الموضوع؟ إنه لمن المفيد حقًا أن يتم تنفيذ عامل التوزيع للتعدادات.

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

@ m93a آه ، فأنت تعني أن extends هنا ساريًا يحتوي على المزيد من دلالات _copying_ (لقيم التعداد من A إلى B )؟ ثم ، نعم ، تظهر المفاتيح بشكل جيد.

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

لهذا السبب ، إذا كان class B extends A ، فإننا نضمن أن B يمكن تخصيصه لـ A ، وبالتالي على سبيل المثال let a: A = new B(); سيكون جيدًا تمامًا.

ولكن باستخدام التعدادات و extends ، لن نتمكن من القيام بـ let a: A = B.b; ، لأنه لا يوجد مثل هذا الضمان المقابل. وهو ما أشعر به غريبًا بالنسبة لي ؛ ينقل extends هنا مجموعة معينة من الافتراضات حول ما يمكن القيام به ، ولا يتم استيفائها بالتعدادات.

masak أعتقد أنك قريب من التصحيح لكنك وضعت افتراضًا صغيرًا غير صحيح. يمكن تعيين B إلى A في حالة enum B extends A حيث تعني كلمة "قابلة للتخصيص" أن جميع القيم التي يوفرها A متوفرة في B. عندما قلت أن let a: A = B.b ، فأنت تفترض أن القيم في B يجب أن تكون متوفر في A ، وهي ليست نفس القيم التي يتم let a: A = B.a إلى A.

هذا واضح باستخدام فئات مثل في المثال التالي:

class A {
 a() {}
}

class B extends A {
 b() {}
}

let a: A = new B();

a.a();  // valid
a.b();  // invalid via type system since `a` is typed as `A`

رابط ملعب TypeScript

invalid access

قصة قصيرة طويلة ، أعتقد أنها توسع المصطلحات الصحيحة لأن هذا هو بالضبط ما يتم فعله. في المثال enum B extends A ، يمكنك دائمًا توقع احتواء التعداد B على جميع القيم المحتملة للتعداد A ، لأن B هي "فئة فرعية" (مجموعة فرعية؟ ربما توجد كلمة أفضل لهذا) من A وبالتالي قابل للتخصيص لـ A.

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

@ julian-sf أعتقد أنني أتفق مع كل ما كتبته ...

... لكن ...: وجه مبتسم قليلاً:

كما أثيرت إشكالية هنا ، ماذا عن هذا الوضع؟

// example from OP
enum BasicEvents {
    Start = "Start",
    Finish = "Finish"
};

// extend enum using "extends" keyword
enum AdvEvents extends BasicEvents {
    Pause = "Pause",
    Resume = "Resume"
};

بالنظر إلى أن Pause هو _instance_ AdvEvents و AdvEvents _extends_ BasicEvents ، هل تتوقع أيضًا أن يكون Pause _طريقة _ BasicEvents ؟ (لأن هذا يبدو أنه يتبع كيفية تفاعل علاقات المثيل / الميراث عادةً.)

من ناحية أخرى ، فإن اقتراح القيمة الأساسية للتعدادات (IMHO) هو أنها _ مغلقة _ / "نهائية" (كما هو الحال في ، غير قابلة للتوسيع) بحيث يمكن لشيء مثل عبارة switch أن يفترض الإجمالي. (وبالتالي ، فإن القدرة على أن تكون قادرًا على _extend_ ما يعنيه أن تكون BasicEvent AdvEvents نوعًا من المفاجأة الأقل للتعداد.)

لا أعتقد أنه يمكنك استخدام أكثر من خاصيتين من الخصائص الثلاث التالية:

  • يتم إغلاق التعدادات / نهائية / إجمالي المتوقع
  • علاقة extends بين إعلانين enum
  • الافتراض (المعقول) أنه إذا كان b مثيلًا لـ B و B extends A ، فإن b هو مثيل A

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

الافتراض (المعقول) أنه إذا كان b مثيلًا لـ B و B يمتد A ، فإن b هو مثيل لـ A

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

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

const enum A { a = 'a' }
const enum B extends A { b = 'b' }

const foo = (a: A) => console.log(a);
const bar = (b: B) => foo(b);

bar(B.a); // 'a'
bar(B.b); // uh-oh, b doesn't exist on A, so foo would get unexpected behavior

// HOWEVER, this would work just fine...

const baz = (a: A) => bar(a);

baz(A.a); // 'a'
baz(B.a); // 'a'
baz(B.b); // compiler error as expected...

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

@ julian-sf

أعتقد أن هذا المنطق مضلل نوعًا ما لأن ثنائية المثيل / الفئة لا يمكن حقًا تخصيصها للتعداد. Enums ليست فئات وليس لديهم أمثلة.

أنت محق تمامًا في ظاهر الأمر.

  • نظرًا لأنه بناء لغة مستقل ، فإن التعداد يحتوي على أعضاء ، وليس حالات. يتم استخدام مصطلح "عضو" في كل من الكتيب ومواصفات اللغة. (يستخدم C # و Python بالمثل مصطلح "عضو". تستخدم Java "ثابت التعداد" ، لأن كلمة "عضو" لها معنى زائد في Java.)
  • من منظور الكود المُجمَّع ، يحتوي التعداد على _خصائص_ ، يرسم خرائط في كلا الاتجاهين - من الأسماء إلى القيم والقيم إلى الأسماء. مرة أخرى ، لا الحالات.

بالتفكير في هذا ، أدركت أنني ملون قليلاً من خلال طريقة Java في التعداد. في Java ، تكون قيم التعداد مثيلات فعليًا لنوع التعداد الخاص بها. من ناحية التنفيذ ، يعد التعداد فئة تمتد لفئة Enum . (لا يُسمح لك بالقيام بذلك يدويًا ، عليك أن تمر عبر الكلمة الرئيسية enum ، ولكن هذا ما يحدث تحت الغطاء.) الشيء الجميل في هذا هو أن التعدادات تحصل على جميع فئات وسائل الراحة: هم يمكن أن تحتوي على حقول ، مُنشئين ، طرق ... في هذا التطبيق ، قم بتعداد الأعضاء _are_ مثيل. (يقول JLS الكثير.)

لاحظ أنني لا أقترح أي تغييرات على دلالات تعداد TypeScript. على وجه الخصوص ، أنا لا أقول أن TypeScript يجب أن يتغير إلى استخدام نموذج Java للتعدادات. أنا _am_ أقول أنه من المفيد / الثاقب وضع "فهم" فئة / مثيل على قمة التعداد / أعضاء التعداد. ليست "enum _is_ class" أو "anum member _ is_ anثيل" ... ولكن هناك أوجه تشابه تنقل.

ما أوجه الشبه؟ أولا وقبل كل شيء ، اكتب العضوية.

enum Foo { A, B, C }
enum Bar { X, Y, Z }

let foo: Foo = Foo.C;
foo = Bar.Z;

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

(سنتجاهل ، لأغراض هذه الوسيطة ، حقيقة أن let foo: Foo = 2; هي أيضًا قانونية من الناحية اللغوية ، وأن قيم number بشكل عام قابلة للتخصيص إلى متغيرات من نوع التعداد.)

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

مع extends على التعدادات ، تخرج هذه الخاصية من النافذة.

انت تكتب،

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

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

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

فكرت في الحلول أكثر من ذلك بقليل ، والعمل على # 17592 (تعليق) ، يمكنك القيام بعمل أفضل قليلاً من خلال تحديد Events في مساحة القيمة:

enum BasicEvents {
  Start = 'Start',
  Finish = 'Finish'
}
enum AdvEvents {
  Pause = 'Pause',
  Resume = 'Resume'
}
type Events = BasicEvents | AdvEvents;
const Events = {...BasicEvents, ...AdvEvents};

let e: Events = Events.Pause;

من الاختبار الذي أجريته ، يبدو أن Events.Start قد تم تفسيره بشكل صحيح على أنه BasicEvents.Start في نظام النوع ، لذا يبدو أن فحص الشمولية والتحسين النقابي المميز يعملان بشكل جيد. الشيء الرئيسي المفقود هو أنه لا يمكنك استخدام Events.Pause كنوع حرفي ؛ تحتاج AdvEvents.Pause . يمكنك _يمكنك استخدام typeof Events.Pause ويتحول إلى AdvEvents.Pause ، على الرغم من أن الأشخاص في فريقي مرتبكون بهذا النوع من النمط وأعتقد عمليًا أنني سأشجع AdvEvents.Pause عند الاستخدام كنوع.

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

أعتقد أن هذا هو أفضل حل متاح الآن.

شكرا alangpierce : +1:

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

sdwvit لست من الأشخاص الأساسيين ، ولكن من وجهة نظري ، فإن اقتراح بناء الجملة التالي (من OP ، ولكن أعيد اقتراحه مرتين بعد ذلك) سيجعل الجميع سعداء ، دون أي مشاكل معروفة:

// extend enum using spread
enum AdvEvents {
    ...BasicEvents,
    Pause = "Pause",
    Resume = "Resume"
};

من شأنه أن يجعل _me_ سعيدًا ، لأنه يعني تنفيذ ميزة "تكرار جميع الأعضاء في هذا التعداد الآخر" التي تبدو مفيدة _ بدون _ باستخدام extends ، والتي أعتبرها مشكلة للأسباب التي ذكرتها. يتجنب بناء الجملة ... هذه المشكلات عن طريق النسخ وليس التوسيع.

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

masak شكرا لك على الرد. علي الآن أن أتصفح كل تاريخ المناقشة. سوف نعود اليك بعد :)

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

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

  • هل يجب أن نسمح بالتمديد من تعدادات متعددة؟ هل يجب أن يكون لديهم جميعًا قيم حصرية متبادلة؟ أم أننا نسمح بتداخل القيم؟ الأولوية على أساس الترتيب المعجمي؟

  • هل يمكن أن تتجاوز التعدادات الموسعة قيم التعدادات الموسعة؟

  • هل يجب أن تظهر التعدادات الموسعة على أنها بداية قائمة القيم أم يمكن أن تكون بأي ترتيب؟ أفترض أن القيم المحددة لاحقًا لها أولوية أعلى؟

  • أفترض أن القيم الرقمية الضمنية ستستمر 1 بعد القيمة القصوى للتعدادات الرقمية الموسعة.

  • اعتبارات خاصة لأقنعة البت؟

إلخ.

JeffreyMercado هذه أسئلة جيدة ومناسبة لمن يأمل في محاولة التنفيذ. :ابتسامة:

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

  • أفترض أننا سنرغب فقط في السماح بتوسيع التعداد باستخدام تعداد من "نفس النوع" (يمتد العدد الرقمي ويمتد السلسلة سلسلة)

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

  • هل يجب أن نسمح بالتمديد من تعدادات متعددة؟ هل يجب أن يكون لديهم جميعًا قيم حصرية متبادلة؟ أم أننا نسمح بتداخل القيم؟ الأولوية على أساس الترتيب المعجمي؟

نظرًا لأنه ينسخ الدلالات التي نتحدث عنها ، فإن تكرار تعدادات متعددة يبدو "أفضل" من الميراث المتعدد على غرار C ++. لا أرى مشكلة على الفور في ذلك ، خاصة إذا واصلنا البناء على تشبيه انتشار الكائن: let newEnum = { ...enumA, ...enumB };

هل يجب أن يكون لدى جميع الأعضاء قيم حصرية متبادلة؟ الشيء المحافظ هو أن تقول "نعم". مرة أخرى ، يوفر لنا تشبيه انتشار الكائن دلالات بديلة: الأخير يفوز.

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

  • هل يمكن أن تتجاوز التعدادات الموسعة قيم التعدادات الموسعة؟

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

  • هل يجب أن تظهر التعدادات الموسعة على أنها بداية قائمة القيم أم يمكن أن تكون بأي ترتيب؟ أفترض أن القيم المحددة لاحقًا لها أولوية أعلى؟

كنت سأقول أولاً أن هذا مهم فقط إذا ذهبنا مع دلالات "الأخير يفوز" للتغلب.

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

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

  • أفترض أن القيم الرقمية الضمنية ستستمر 1 بعد القيمة القصوى للتعدادات الرقمية الموسعة.

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

  • اعتبارات خاصة لأقنعة البت؟

أعتقد أن هذا مشمول بالقاعدة المذكورة أعلاه.

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

export enum Unit {
    SECONDS,
    MINUTES,
    HOURS,
    DAYS,
    WEEKS,
    MONTHS,
    YEARS,
    DECADES,
    CENTURIES,
    MILLENNIA
}

interface Labels {
    SINGULAR: Record<Unit, string>
    PLURAL: Record<Unit, string>
    LAST: string;
    DELIM: string;
    NOW: string;
}

export const EnglishLabels: Labels = {
    SINGULAR: {
        [Unit.SECONDS]: ' second',
        [Unit.MINUTES]: ' minute',
        [Unit.HOURS]: ' hour',
        [Unit.DAYS]: ' day',
        [Unit.WEEKS]: ' week',
        [Unit.MONTHS]: ' month',
        [Unit.YEARS]: ' year',
        [Unit.DECADES]: ' decade',
        [Unit.CENTURIES]: ' century',
        [Unit.MILLENNIA]: ' millennium'
    },
    PLURAL: {
        [Unit.SECONDS]: ' seconds',
        [Unit.MINUTES]: ' minutes',
        [Unit.HOURS]: ' hours',
        [Unit.DAYS]: ' days',
        [Unit.WEEKS]: ' weeks',
        [Unit.MONTHS]: ' months',
        [Unit.YEARS]: ' years',
        [Unit.DECADES]: ' decades',
        [Unit.CENTURIES]: ' centuries',
        [Unit.MILLENNIA]: ' millennia'
    },
    LAST: ' and ',
    DELIM: ', ',
    NOW: ''
}

illeatmyhat هذا استخدام لطيف للتعدادات ، لكن ... أخفق في رؤية كيف يمكن اعتباره توسيعًا لتعداد حالي. ما تفعله هو _ استخدام_ التعداد.

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

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

قد ينسى الشخص الذي أضاف عضوًا في قائمة enum لاحقًا إضافة مفتاح مطابق في السجل SINGULAR و PLURAL في جميع حالات Label .)

على الأقل في بيئتي ، يحدث خطأ عندما يكون أحد أعضاء التعداد مفقودًا إما من SINGULAR أو PLURAL . أعتقد أن النوع Record يؤدي وظيفته.

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

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

على الأقل في بيئتي ، يحدث خطأ عندما يكون أحد أعضاء التعداد مفقودًا إما من SINGULAR أو PLURAL . أعتقد أن النوع Record يؤدي وظيفته.

أوه! سمسم. ونعم ، هذا يجعلها أكثر إثارة للاهتمام. أرى ما تقصده في البداية للوصول إلى وراثة التعداد ثم الهبوط في النهاية على النمط الخاص بك. قد لا يكون هذا شيئًا منعزلًا ؛ "مشاكل X / Y" شيء حقيقي. قد يبدأ المزيد من الأشخاص بفكرة "أريد تمديد MyEnum " ، لكن ينتهي بهم الأمر باستخدام Record<MyEnum, string> كما فعلت.

الرد على masak :

مع الامتدادات على التعداد ، تخرج هذه الخاصية من النافذة.

انت تكتب،

@ julian-sf: أفهم وأوافق على مبدأ التعداد المغلق (في وقت التشغيل). لكن تمديد وقت التجميع لن ينتهك مبدأ الإغلاق في وقت التشغيل ، حيث سيتم تحديدها وبناءها جميعًا بواسطة المترجم.

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

كلما فكرت في هذا الأمر ، فأنت على حق تمامًا. يجب إغلاق Enums. تعجبني حقًا فكرة "تأليف" التعدادات لأنني أعتقد أن هذا حقًا هو جوهر الأمر الذي نريده هنا 🥳.

أعتقد أن هذا الترميز يلخص مفهوم "دمج تعدادين منفصلين معًا" بأناقة تامة:

enum ComposedEnum = { ...EnumA, ...EnumB }

لذا اعتبر أن استقالتي على استخدام المصطلح extends 😆


تعليقات على إجابات masak على أسئلة JeffreyMercado :

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

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

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

ربما مجرد تشجيع قوي لا؟

  • هل يجب أن نسمح بالتمديد من تعدادات متعددة؟ هل يجب أن يكون لديهم جميعًا قيم حصرية متبادلة؟ أم أننا نسمح بتداخل القيم؟ الأولوية على أساس الترتيب المعجمي؟

نظرًا لأنه ينسخ الدلالات التي نتحدث عنها ، فإن تكرار تعدادات متعددة يبدو "أفضل" من الميراث المتعدد على غرار C ++. لا أرى مشكلة على الفور في ذلك ، خاصة إذا واصلنا البناء على تشبيه انتشار الكائن: دع newEnum = {... enumA، ... enumB}؛

أتفق بنسبة 100

  • هل يجب أن يكون لدى جميع الأعضاء قيم حصرية متبادلة؟

الشيء المحافظ هو أن تقول "نعم". مرة أخرى ، يوفر لنا تشبيه انتشار الكائن دلالات بديلة: الأخير يفوز.

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

قد تكون هناك أيضًا أمثلة جيدة للرغبة في تجاوز قيمة ما (على الرغم من أنني لا أعرف ما هي هذه القيم).

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

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

  • أفترض أن القيم الرقمية الضمنية ستستمر 1 بعد القيمة القصوى للتعدادات الرقمية الموسعة.

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

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

@ julian-sf

لذا اعتبر أن استقالتي من استخدام المصطلح يمتد

: +1: جمعية الحفاظ على أنواع السوم تهتف من الخطوط الجانبية.

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

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

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

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

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

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

إنه لأمر مخز أن تستمر 3 سنوات في هذه المحادثة.

@ jmitchell38488 أود إسقاط إعجاب لتعليقك ، لكن جملتك الأخيرة غيرت رأيي. هذه مناقشة ضرورية ، نظرًا لأن الحل المقترح سيعمل ، ولكنه يشير أيضًا إلى إمكانية توسيع الفئات والواجهات بهذه الطريقة. إنه تغيير كبير قد يخيف بعض مبرمجي اللغات التي تشبه c ++ من استخدام الكتابة المطبوعة ، نظرًا لأنك تنتهي في الأساس بطريقتين للقيام بنفس الشيء ( class A extends B و class A { ...(class B {}) } ). أعتقد أنه يمكن دعم كلا الطريقتين ، ولكن بعد ذلك نحتاج إلى extend للتعدادات أيضًا من أجل الاتساق.

masak wdyt؟ ^

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

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

masak wdyt؟ ^

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

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

هل يعمل أي شخص على هذه الميزة؟

هل يعمل أي شخص على هذه الميزة؟

تخميني هو لا ، لأننا لم ننتهي من المناقشة حول هذا الموضوع ، هاها!

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

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

سأحب هذه الميزة أيضًا. لقد مرت 37 شهرًا ، ونأمل أن يتم إحراز تقدم قريبًا

ملاحظات من الاجتماع الأخير:

  • نحن نحب صيغة الانتشار ، لأن extends يتضمن نوعًا فرعيًا ، بينما يؤدي "تمديد" التعداد إلى إنشاء نوع فائق.

    enum BasicEvents {
     Start = "Start",
     Finish = "Finish"
    }
    enum AdvEvents {
     ...BasicEvents,
     Pause = "Pause",
     Resume = "Resume"
    }
    
  • الفكرة هي أن AdvEvents.Start سيتحول إلى نفس نوع الهوية مثل BasicEvents.Start . ويترتب على ذلك إمكانية تعيين النوعين BasicEvents.Start و AdvEvents.Start لبعضهما البعض ، والنوع BasicEvents سيكون قابلاً للتخصيص إلى AdvEvents . نأمل أن يكون هذا منطقيًا ، ولكن من المهم ملاحظة أن هذا يعني أن السبريد ليس مجرد اختصار نحوي - إذا قمنا بتوسيع السبريد إلى ما يبدو أنه يعني:

    enum BasicEvents {
     Start = "Start",
     Finish = "Finish"
    }
    enum AdvEvents {
     Start = "Start",
     Finish = "Finish",
     Pause = "Pause",
     Resume = "Resume"
    }
    

    هذا له سلوك مختلف - هنا ، BasicEvents.Start و AdvEvents.Start _لا يمكن تخصيصهما لبعضهما البعض ، بسبب الجودة غير الشفافة لتعدادات السلاسل.

    نتيجة ثانوية أخرى لهذا التطبيق هو أنه من المحتمل أن يتم تسلسل $ # AdvEvents.Start كـ BasicEvents.Start في إصدار معلومات سريعة وإعلان (على الأقل في حالة عدم وجود سياق نحوي لربط العضو بـ AdvEvents —يمكن أن يؤدي التمرير فوق التعبير الحرفي AdvEvents.Start بشكل معقول إلى الحصول على معلومات سريعة تقول AdvEvents.Start ، ولكن قد يكون من الواضح أكثر عرض BasicEvents.Start مع ذلك).

  • لن يُسمح بهذا إلا في تعداد السلسلة.

أود أن أجرب هذا.

للتوضيح: هذا غير معتمد للتنفيذ.

هناك نوعان من السلوكيات المحتملة هنا ، وكلاهما يبدو سيئًا.

الخيار 1: إنه سكر في الواقع

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

الخيار 2: إنه ليس سكر في الواقع

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

ما هو السلوك الذي يريده الناس ولماذا؟

لا أريد الخيار الأول ، لأنه يقود إلى مفاجآت كبيرة.

أريد الخيار 2 ، لكني أرغب في الحصول على دعم نحوي كافٍ للتغلب على الجانب السلبي @ aj-r المذكور ، لذا يمكنني كتابة let e: Events = Events.Pause; من مثاله. الجانب السلبي ليس فظيعًا ، لكنه مكان لا يستطيع فيه التعداد الموسع إخفاء التنفيذ ؛ لذلك فهو نوع من المقزز.

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

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

ما هو السلوك الذي يريده الناس ولماذا؟

نظرًا لأن الكود يتحدث بصوت أعلى من الكلمات ، وباستخدام المثال من OP:

const BasicEvents = {
    Start: "Start",
    Finish: "Finish"
};
enum AdvEvents {
    ...BasicEvents,
    Pause = "Pause", // We added a new field
    Finish = "Finish2" // Oops, we actually modified a field in the parent enum
};
// The TypeScript compiler should refuse to compile this code
// But after removing the "Finish2" line,
// the TypeScript compiler should successfully handle it as one would normally expect with the spread operator

يعرض هذا المثال الخيار 2 ، أليس كذلك؟ إذا كان الأمر كذلك ، فأنا أرغب بشدة في الخيار 2.

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

أوافق على أن الخيار 2 مقلق إلى حد ما وقد يكون من الأفضل التراجع والتفكير في البدائل. فيما يلي استكشاف لكيفية القيام بذلك دون إضافة أي بناء جملة أو سلوك جديد إلى تعدادات اليوم:

أعتقد أن اقتراحي في https://github.com/microsoft/TypeScript/issues/17592#issuecomment -449440944 هو الأقرب هذه الأيام وقد يكون شيئًا للعمل منه:

type Events = BasicEvents | AdvEvents;
const Events = {...BasicEvents, ...AdvEvents};

أرى مشكلتين رئيسيتين في هذا النهج:

  • إنه أمر محير حقًا أن يتعلم شخص ما TypeScript ؛ إذا لم يكن لديك فهم قوي لمساحة الكتابة مقابل مساحة القيمة ، فيبدو أنها تقوم بالكتابة فوق نوع بثابت.
  • إنه غير مكتمل لأنه لا يسمح لك باستخدام Events.Pause كنوع.

لقد اقترحت سابقًا https://github.com/microsoft/TypeScript/issues/29130 التي من شأنها (من بين أشياء أخرى) معالجة النقطة الثانية ، وأعتقد أنها قد تظل ذات قيمة ، على الرغم من أنها بالتأكيد ستضيف الكثير من الدقة إلى كيف تعمل الأسماء.

فكرة بناء الجملة التي أعتقد أنها ستعالج كلا النقطتين هي بناء جملة بديل const والذي يعلن أيضًا عن نوع:

// Declares a value and a type at the same time with the same name (just like `enum` and `class` already do).
// Requires the right-hand side to be either a unit type or an object where all values are unit types.
// The JS emit would just take out the word "type" and leave everything else.
const type Events = {...BasicEvents, ...AdvEvents};
...
const e: Events.Pause = Events.Pause;
...
// The syntax could also make this pattern more ergonomic.
const type INACCESSIBLE = "INACCESSIBLE";
const response: {name: string, favoriteColor: string} | INACCESSIBLE = INACCESSIBLE;

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

const type BasicEvents = {
  Start: 'Start',
  Finish: 'Finish',
};  // "as const" would be implicit for "const type" declarations.

من المسلم به أن الطريقة التي يجب أن يعمل بها هذا هي أن النوع BasicEvents هو اختصار لـ typeof BasicEvents[keyof typeof BasicEvents] ، والذي قد يكون مركّزًا جدًا على بناء جملة مسمى بشكل عام مثل const type . ربما يكون من الأفضل استخدام كلمة رئيسية مختلفة. سيء جدًا const enum مأخوذ بالفعل 😛.

من هناك ، أعتقد أن الفجوة الوحيدة بين تعدادات (سلسلة) وحرفية الكائن ستكون الكتابة الاسمية. ربما يمكن حل ذلك باستخدام بناء جملة مثل as unique أو const unique type الذي يختار بشكل أساسي سلوك الكتابة الاسمي الشبيه بالتعداد لأنواع الكائنات هذه ، وبشكل مثالي لثوابت السلسلة العادية أيضًا. سيعطي ذلك أيضًا خيارًا واضحًا بين الخيار 1 والخيار 2 عند تحديد Events : تستخدم unique عندما تنوي أن يكون Events نوعًا مميزًا تمامًا (الخيار 1) ، وقمت بحذف unique عندما تريد التخصيص بين Events و BasicEvents (الخيار 2).

مع const type و const unique type ، ستكون هناك طريقة لدمج التعدادات الموجودة بشكل نظيف ، وأيضًا طريقة للتعبير عن التعدادات كمجموعة طبيعية من ميزات TS بدلاً من لمرة واحدة.

ماذا يحصل هنا؟ 😅

واو من 2017 🤪 ، ما المزيد من الملاحظات التي تحتاجها؟

ما المزيد من الملاحظات التي تحتاجها؟

هنا؟؟؟ نحن لا ننفذ الاقتراحات فقط لأنها قديمة!

ما المزيد من الملاحظات التي تحتاجها؟

هنا؟؟؟ نحن لا ننفذ الاقتراحات فقط لأنها قديمة!

نعم. لم أكن متأكدا في أي اتجاه كان.

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

لا أعتقد أنني مؤهل بما يكفي لإبداء رأيي في تصميم اللغة ، لكنني أعتقد أنه يمكنني تقديم ملاحظات بصفتي مطورًا منتظمًا.

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

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

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

أهلا،

اسمحوا لي أن أضيف اثنين سنتي هنا 🙂

السياق الخاص بي

لدي API ، مع وثائق OpenApi التي تم إنشاؤها باستخدام tsoa .

أحد نماذجي له حالة محددة على النحو التالي:

enum EntityStatus {
    created = 'created',
    started = 'started',
    paused = 'paused',
    stopped = 'stopped',
    archived = 'archived',
}

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

enum RequestedEntityStatus {
    started = 'started',
    paused = 'paused',
    stopped = 'stopped',
}

لذلك تم وصف طريقتي بهذه الطريقة:

public setStatus(status: RequestedEntityStatus) {
   this.status = status;
}

بهذا الرمز ، أتلقى هذا الخطأ:

Conversion of type 'RequestedEntityStatus' to type 'EntityStatus' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.

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

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

اقتراحي

لقد وجدت عامل الانتشار أفضل من اقتراح الامتداد ، لكني أود أن أذهب إلى أبعد من ذلك وأقترح ما يلي:

enum EntityStatus {
    created = 'created',
    started = 'started',
    paused = 'paused',
    stopped = 'stopped',
    archived = 'archived',
}

enum RequestedEntityStatus {
    // Pick/Reuse from EntityStatus
    EntityStatus.started,
    EntityStatus.paused,
    EntityStatus.stopped,
}

// Fake enum, just to demonstrate
enum TargetStatus {
    {...RequestedEntityStatus},
    // Why not another spread here?
    //{...AnotherEnum},
    EntityStatus.archived,
}

public class Entity {
    private status: EntityStatus = 'created'; // Why not a cast here, if types are compatible, and deserializable from a JSON. EntityStatus would just act as a type union here.

    public setStatus(requestedStatus: RequestedEntityStatus) {
        if (this.status === (requestedStatus as EntityStatus)) { // Should be OK because types are compatible, but the cast would be needed to avoid comparing oranges and apples
            return;
        }

        if (requestedStatus == RequestedStatus.stopped) { // Should be accessible from the enum as if it was declared inside.
            console.log('Stopping...');
        }

        this.status = requestedStatus;// Should work, since EntityStatus contains all the enum members that RequestedEntityStatus has.
    }

    public getStatusAsStatusRequest() : RequestedEntityStatus {
        if (this.status === EntityStatus.created || this.status === EntityStatus.archived) {
            throw new Error('Invalid status');
        }
        return this.status as RequestedEntityStatus; // We have  eliminated the cases where the conversion is impossible, so the conversion should be possible now.
    }
}

بشكل عام ، يجب أن يعمل هذا:

enum A { a = 'a' }
enum B { a = 'a' }

const a:A = A.a;
const b:B = B.a;

console.log(a === b);// Should not say "This condition will always return 'false' since the types 'A' and 'B' have no overlap.". They do have overlaps

بعبارات أخرى

أرغب في تخفيف القيود المفروضة على أنواع التعداد حتى تتصرف مثل النقابات ( 'a' | 'b' ) أكثر من الهياكل غير الشفافة.

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

enum A { a = 'a', b = 'b' }
enum B { a = 'a' , c = 'c' }
enum C { ...A, c = 'c' }

وثلاثة متغيرات a:A و b:B و c:C

  • يجب أن يعمل c = a ، لأن A مجرد مجموعة فرعية من C ، لذا فإن كل قيمة A هي قيمة صالحة لـ C
  • يجب أن يعمل c = b ، لأن B هو 'a' | 'c' فقط وهما قيمتان صالحتان لـ C
  • يمكن أن يعمل b = a إذا كان من المعروف أن a يختلف عن 'b' (والذي سيساوي النوع 'a' فقط)
  • وبالمثل ، يمكن أن يعمل $ # $ 13 a = b # $ إذا كان من المعروف أن b يختلف عن 'c'
  • b = c يمكن أن يعمل إذا كان من المعروف أن c يختلف عن 'b' (والذي سيساوي 'a'|'c' ، وهو بالضبط ما يمثل B)

أو ربما نحتاج إلى ممثلين صريحين على الجانب الأيمن ، فيما يتعلق بمقارنة المساواة؟

حول نزاعات أعضاء التعداد

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

enum A { a = 'a', b = 'b' }
enum B { a = 'a' , c = 'c' }
enum C {...A, ...B } // OK, equivalent to enum C { a = 'a', b = 'b', c = 'c' }, a is deduplicated
enum D {...A, a = 'd' } // Error : Redefinition of a
enum E {...A, d = 'a' } // Error : Duplicate key with value 'a'

إغلاق

أجد هذا الاقتراح مرنًا وطبيعيًا تمامًا لمطوري TS للعمل معه ، مع السماح بمزيد من أمان النوع (مقارنة بـ as unknown as T ) دون تغيير في الواقع ما هو التعداد. إنه يقدم فقط طريقة جديدة لإضافة أعضاء إلى التعداد ، وطريقة جديدة لمقارنة الأعداد ببعضها البعض.
ما رأيك؟ هل فاتني بعض المشكلات الواضحة في هندسة اللغة والتي تجعل هذه الميزة غير قابلة للتحقيق؟

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