إثارة تحذيرات الخصائص الزائدة باستخدام الأنواع العودية والأنواع المعينة في TypeScript


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

type MappedType<T, V> = {
  [K in keyof T] : V
}

type MappingFn<T, V> = (param : MappedType<T, V>) => V;

type SelfReferentialType<T, V> = {
  [key : string] : T | MappingFn<SelfReferentialType<T, V>, V>
}

const example : SelfReferentialType<number, string> = {
  fieldA : 99,
  fieldB : ({ fieldA }) => fieldA.toUpperCase(), //works as expected
  fieldC : ({ fieldD }) => fieldD.toUpperCase() //fieldD does not exist, so I would like this to trigger a warning
}

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

{
  fieldA : 'ninety-nine',
  fieldB : 'a function',
  fieldC : 'another function'
}

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

الإجابة رقم 1

ستحتاج إلى نوع عام مثل SelfReferentialType<T, U, K> حيث K هي مجموعة المفاتيح التي يمتلكها كائنك.

type SelfReferentialType<T, U, K extends PropertyKey> =
    { [P in K]: T | ((o: { [P in K]: U }) => U) }

من المفترض أنك تريد تحديد T و U يدويًا ولكن تعطي المُجمّع القدرة على استنتاج K بناءً على الكائن الحرفي الذي تستخدمه. لسوء الحظ، TypeScript لا تسمح باستنتاج نوع الوسيط للأنواع العامة مباشرةً، ستحتاج إلى استدعاء دالة مساعدة عامة ليحدث ذلك. علاوة على ذلك، TypeScript لا تسمح لك بتحديد بعض وسائط النوع بينما يستنتج الباقي، كما هو مطلوب في microsoft/TypeScript#26242. إما كل شيء أو لا شيء. الحل القياسي هو كتابة دالة مُكررة (curried). في حالتك، يمكن أن يبدو مثل هذا:

const selfReferentialType = <T, U>() => <K extends PropertyKey>(
    srt: SelfReferentialType<T, U, K>
) => srt;

واستخدمه هكذا:

const example = selfReferentialType<number, string>()({
    fieldA: 99,
    fieldB: ({ fieldA }) => fieldA.toUpperCase(), // okay
    fieldC: ({ fieldD }) => fieldD.toUpperCase() // error
})
// const example: SelfReferentialType<number, string, "fieldA" | "fieldB" | "fieldC">

من الغريب قليلاً استدعاء دالة باستخدام معاملات النوع دون معاملات الدالة ثم استدعاء القيمة المرجعة مباشرة كدالة أخرى، لكنه يتصرف كما تريد: كائن example هو من النوع SelfReferentialType<number, string, "fieldA" | "fieldB" | "fieldC">، لذلك يعرف المترجم أن الحقول لا تشمل "fieldD" وستحصل على الخطأ المطلوب.

رابط الملعب إلى الكود