تعيين كائن حرفي إلى نوع عام في تايبسكريبت


أنا أبني واجهة FormConditions حيث سيكون لدي كائن بمفاتيح عشوائية، كل منها هو نسخة من فئة تنفذ واجهة Condition. أريد تعيين هذا الكائن الحرفي إلى متغير، وأن يكون نوع الكائن الناتج A) يستجيب فقط للمفاتيح الموجودة في الكائن الحرفي، و B) يحترم أي توريث فرعي أو امتداد آخر قد يكون لكل من تلك المفاتيح.

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

interface Condition {
    name: string
    id: number
}

type FormConditions<T extends Record<string, Condition>> = {
    [P in keyof T]: T[P]
}

class SimpleCondition implements Condition {
    constructor(public name: string, public id: number) {}
}

class ListCondition<T> implements Condition {
    constructor(public name: string, public id: number, public entries: T[]) {}
}

// This is a passthrough function just to make the types work
function makeFormConditions<T extends Record<string, Condition>>(obj: T): FormConditions<T>  {
    return obj;
}

// Would prefer to avoid the function call to make types work
const conditions = makeFormConditions({
    simpleOne: new SimpleCondition('simpleOne', 1),
    simpleTwo: new SimpleCondition('simpleTwo', 2),
    list: new ListCondition('list', 3, ['foo', 'bar'])
})


// This works but is redundantly verbose
// const conditions : FormConditions<{
//     simpleOne: SimpleCondition;
//     simpleTwo: SimpleCondition;
//     list: ListCondition<string>;
// }> = {
//     simpleOne: new SimpleCondition('simpleOne', 1),
//     simpleTwo: new SimpleCondition('simpleTwo', 2),
//     list: new ListCondition('list', 3, ['foo', 'bar'])
// }
//
// would instead prefer to not use the function or be
// really specific about the type declaration:
// const conditions : FormConditions = {
//     simpleOne: new SimpleCondition('simpleOne', 1),
//     simpleTwo: new SimpleCondition('simpleTwo', 2),
//     list: new ListCondition('list', 3, ['foo', 'bar'])
// }

conditions.list.name
conditions.list.entries
conditions.simpleOne.name
conditions.simpleOne.entries // error, as expected

إليك رابط ملعب تايب سكريبت لما ذُكر أعلاه.

الإجابة رقم 1

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

توسيع الواجهة Condition لتشمل جميع خصائص الأنواع الفرعية

يمكن توسيع تعريف Condition ليقبل مصفوفة entries اختيارية، بحيث يمكن أن تحتوي FormConditions<E, T extends Record<string, Condition<E>>> على كل من SimpleConditions و ListConditions. وهذا له أثر جانبي غير مرغوب فيه هو أن نسخ SimpleCondition قد تشير إلى خاصية entries المفقودة بدون حدوث خطأ.

interface Condition<E> {
    name: string
    id: number
    entries?: E[]
}

type FormConditions<E, T extends Record<string, Condition<E>>> = {
    [P in keyof T]: T[P]
}

class SimpleCondition<E = never> implements Condition<E> {
    constructor(public name: string, public id: number) {}
}

class ListCondition<E> implements Condition<E> {
    constructor(public name: string, public id: number, public entries: E[]) {}
}

const conditions: FormConditions<string, Record<string, Condition<string>>> = {
    simpleOne: new SimpleCondition('simpleOne', 1),
    simpleTwo: new SimpleCondition('simpleTwo', 2),
    list: new ListCondition('list', 3, ['foo', 'bar'])
}

conditions.list.name;
conditions.list.entries;
conditions.simpleOne.name;
conditions.simpleOne.entries;  // Expected error; however, no error, since `entries` is optional parameter.

قم بتقييد واجهة Condition لتشمل فقط name و id

نظرًا لأن Condition محدودة، يحدث خطأ (كما هو متوقع) عند محاولة الوصول إلى entries على نسخ من SimpleCondition. ومع ذلك، في سياق type FormConditions<E, T extends Record<string, Condition>>، تؤدي نسخ ListCondition إلى حدوث خطأ عند الإشارة إلى entries، نظرًا لأن النوع قد تم تضييقه إلى Condition.

interface Condition {
    name: string
    id: number
}

type FormConditions<E, T extends Record<string, Condition>> = {
    [P in keyof T]: T[P]
}

class SimpleCondition<E = never> implements Condition {
    constructor(public name: string, public id: number) {}
}

class ListCondition<E> implements Condition {
    constructor(public name: string, public id: number, public entries: E[]) {}
}

const conditions: FormConditions<string, Record<string, Condition>> = {
    simpleOne: new SimpleCondition('simpleOne', 1),
    simpleTwo: new SimpleCondition('simpleTwo', 2),
    list: new ListCondition('list', 3, ['foo', 'bar'])
}

conditions.list.name;
conditions.list.entries; // Error: Property 'entries' does not exist on type 'Condition'.
conditions.simpleOne.name;
conditions.simpleOne.entries; // error (as expected - Good)