منع نوع عائد الاستدعاء الرجعي من تضمين خصائص إضافية في TypeScript
افرض أن لدي واجهة Foo ودالة bar تأخذ دالة رد نداء تعيد Foo.
interface Foo {
foo: string;
}
function bar(callback: () => Foo): Foo {
return callback();
}
عندما أنادي هذا، إذا أضفت خصائص إلى القيمة المرجعة بخلاف foo، فإن المترجم لا يطلق خطأ.
// throws an error as expected
const x: Foo = { foo: 'abc', baz: 'def' };
// does not throw an error
const y = bar(() => ({ foo: 'abc', baz: 'def' }));
كيف يمكنني التأكد من أن الكائن لا يحتوي على خصائص أخرى غير المحددة في Foo؟ لقد حاولت استخدام type بدلاً من interface مع نفس النتائج. إليك ملعب TypeScript.
الإجابة رقم 1
يحدث التحقق من الخصائص الزائدة فقط في ظروف محددة، وقد وجدت حالة لا يحدث فيها. الأنواع (بما في ذلك الواجهات مثل Foo) في TypeScript مفتوحة وقابلة للتوسيع؛ يمكنك تعريف واجهة مثل
interface Baz extends Foo {
baz: string;
}
وأرجع Baz داخل دالة النداء bar()، وهو Foo صالح:
const baz: Baz = { foo: "abc", baz: "def" };
const foo: Foo = baz; // no error
const z = bar(() => baz); // no error...
لا يحتوي TypeScript على نوع ملموس يقابل تمامًا Foo بدون خصائص إضافية. ومع ذلك، من الممكن استخدام قيد عام يمثل "Foo بدون خصائص إضافية معروفة". لاحظ التحفظ "المعروف"، سيظهر لاحقًا:
function bar<
F extends Foo & { [K in keyof F]: K extends keyof Foo ? Foo[K] : never }
>(callback: () => F): Foo {
return callback();
}
هنا لقد جعلت bar() دالة عامة تأخذ دالة رد نداء من نوع () => F، حيث أن F مقيدة بـ Foo، وكذلك بنوع شرطي مخطط. يأخذ هذا النوع المخطط أي مفاتيح إضافية معروفة ويغير نوع الخاصية إلى never، وهو أمر غير ممكن. على سبيل المثال، إذا كان F هو Baz، فإن القيد سيكون Foo & { foo: string, baz: never }. نظرًا لأن Baz لا تمتد إلى { foo: string, baz: never }، فستحصل على خطأ إذا حاولت استدعاء bar() على دالة معروفة أنها ترجع Baz:
const y = bar(() => ({ foo: "abc", baz: "def" })); // error, generic constraint failed
// -----------------------------> ~~~
// "string" is not assignable to "never"
const z = bar(() => baz); // error! "string" is not assignable to "never"
// ---------------> ~~~
هذا على الأقل سيمنع الناس من إعادة الكائنات الحرفية مع خصائص إضافية.
ومع ذلك، فهي ليست حلاً مثالياً. المتغير foo السابق تم تمييزه بالنوع Foo، لكنه تم تهيئته بقيمة من النوع Baz. المجمع يعرف فقط أو يهتم بأن foo من النوع Foo. لقد نسي تمامًا أن foo هو أيضًا من النوع Baz. لذلك فهو لا يعرف أن foo لديه خاصية "baz"، ولن يمنع هذا:
const oops = bar(() => foo); // still no error...
// foo has a "baz" property but the compiler forgot
// no way around this in TypeScript, sorry
لا يوجد ما يمكن فعله هناك، أخشى ذلك، سوى إجراء فحص وقت التشغيل. إذا كنت تريد حقًا رؤية الأنواع الدقيقة في TypeScript، فقد ترغب في الانتقال إلى قضية GitHub ذات الصلة وإعطائها 👍. لكن ربما يكون الحل أعلاه كافيًا لحالتك.
حسنًا؛ آمل أن يكون ذلك مفيدًا. حظًا سعيدًا!
رابط إلى الكود
