لماذا يقوم TypeScript باستنتاج هذا النوع لعنصر المصفوفة مع اتحاد لأنواع المصفوفة؟
أواجه صعوبة في فهم سبب قيام TypeScript باستنتاج نوع معين لعنصر المصفوفة عندما يكون النوع نوع اتحاد وأنواعها "تتداخل". لقد قلصت المشكلة إلى هذا الحد الأدنى من الاستنساخ:
interface Base {
id: string;
}
interface Child {
id: string;
parentId: string;
}
interface Obj {
nodes1: Base[] | Child[];
nodes2: (Base | Child)[];
}
const obj: Obj = {
nodes1: [],
nodes2: []
};
const node1 = obj.nodes1[0]; // typed as Base
const node2 = obj.nodes2[0]; // typed as Base | Child
إذا قمت بتغيير Base بحيث لا يكون مجرد مجموعة فرعية من خصائص Child مثل هذا:
interface Base {
baseId: string;
}
الآن، يتم استنتاج كل من node1 و node2 على أنهما Base | Child. هذا ما كنت أتوقعه في المرة الأولى. في الكود الفعلي الخاص بي، يمكن أن يكون المصفوفة إما كلها من نوع Base أو كلها من نوع Child، لذا يبدو أن الكتابة أفضل على شكل Base[] | Child[]، لكنني اضطررت لاستخدام (Base | Child)[] في الوقت الحالي. كنت أستطيع إجراء إعادة هيكلة أكبر لإدخال الأنواع العامة (generics) لكنها ليست تغييرًا بسيطًا.
لماذا يتم استنتاج النوع كـ Base فقط وليس Base | Child؟
الإجابة رقم 1
انظر إلى microsoft/TypeScript#43667 للحصول على إجابة رسمية. هذه قيود تصميمية في TypeScript.
كما قد تكون على علم: في نظام النوع الهيكلي لـ TypeScript، Child هو نوع فرعي من Base على الرغم من أنه لم يُعلن عنه صراحةً على هذا النحو. لذلك كل قيمة من نوع Child هي أيضًا قيمة من نوع Base (على الرغم من أن العكس ليس صحيحًا). هذا يعني أن Child | Base يعادل Base... على الرغم من أن المترجم ليس دائمًا صارمًا في تبسيط الأول إلى الأخير. (قارن هذا بالسلوك مع شيء مثل "foo" | string، الذي يتم تبسيطه دائمًا فورًا إلى string بواسطة المترجم.)
غالبًا ما يكون تقليل النوع الفرعي مرغوبًا فيه، ولكن هناك بعض الأماكن التي يكون فيها سلوك Child | Base مختلفًا بشكل ملحوظ عن Base، مثل فحوصات الخصائص الزائدة، تلميحات IntelliSense، أو نوع الحماية غير الآمن التي تحدث مع عامل in. لم توضح لماذا يهمك أن تحصل على Base بدلاً من Child | Base، ولكن من المفترض أن يكون السبب أحد هذه الاختلافات الملحوظة أو شيء مشابه لها.
نصيحتي هنا هي أولاً أن تفكر بعناية فيما إذا كنت بحاجة حقًا إلى هذا التمييز أم لا. إذا كان الأمر كذلك، فقد تفكر في منع Base من أن يكون نوعًا فرعيًا من Child، ربما عن طريق إضافة خاصية اختيارية له:
interface Base {
id: string;
__baseMarker?: never; // <-- added property
}
إذن الآن نحن نقول أن Base يحتوي على id بقيمة string و إما لا يحتوي على خاصية __baseMarker، أو يحتوي على واحدة بقيمة من نوع never (وهو أمر مستحيل). لذلك فهو في الأساس "لا توجد خاصية __baseMarker" (حسنًا، قد تكون undefined). هذا ليس مختلفًا كثيرًا عن تعريفك الأصلي، لكن الآن Child extends Base خاطئ و Child | Base ليس مكافئًا لـ Base:
const node1 = obj.nodes1[0]; // typed as Base | Child
رابط الملعب إلى الكود
