Kodokon kodokon.com

التضييق: typeof وin وinstanceof وحُرّاس الأنواع

تتقن آليات تضييق الأنواع وتكتب مسنداتك الخاصة لجعل الاتحادات أكثر موثوقية.

10 دقيقة · 3 أسئلة

افتح هذا الدرس في Kodokon

التضييق (Narrowing) هو الآلية التي تُدقّق بها TypeScript نوعا واسعا (اتحاد، أو unknown) فتحوّله إلى نوع أدق بتحليل تدفق التحكم. أنت تستعمله بالفعل دون أن تنتبه: كل if على قيمة ذات نوع يُطلق تحليلا. يتتبّع المُصرّف ثلاث عائلات من الحُرّاس المدمجين: typeof للأنواع الأولية، وinstanceof لنُسخ الأصناف، وin للتحقق من وجود خاصية. اختيارها بحكمة يجنّبك التحويلات المتهورة بـas، التي تُطفئ التحقق بدل أن توجّهه.

TYPESCRIPT
function formatValue(
  value: string | number | Date
): string {
  if (typeof value === "string") {
    return value.trim();
  }
  if (value instanceof Date) {
    return value.toISOString();
  }
  return value.toFixed(2);
}
كل فرع يضيّق النوع؛ الفرع الأخير لا يمكن أن يكون سوى number.

عامل in لا يُقدّر بثمن حين تتعامل مع كائنات مختلفة بنيويا لا تملك حقلا مميِّزا. لكن احذر: in يختبر وجود مفتاح، بما في ذلك المفاتيح الموروثة، لا نوعه. وعلى البيانات الخارجية (واجهات برمجية، JSON)، فإنه يضيّق وفق تصريحاتك أنت، لا وفق ما هو موجود فعلا وقت التشغيل. أما الفخ الكلاسيكي في الواقع: فحص typeof value === "object" يسمح بمرور null، لأن typeof null هو "object" في JavaScript. أضف دائما فحصا صريحا لـnull.

TYPESCRIPT
interface Cat { meow(): void }
interface Dog { bark(): void }

function speak(animal: Cat | Dog): void {
  if ("meow" in animal) {
    animal.meow();
  } else {
    animal.bark();
  }
}
التضييق بوجود خاصية عبر عامل in.

حين يصبح منطق الحارس قابلا لإعادة الاستعمال أو أعقد من أن يتولاه تحليل التدفق، اكتب حارس نوع مخصصا: دالة يُشرح نوع عائدها بـvalue is T. عندها يثق المُصرّف بتنفيذك. إنه عقد: إن كان فحصك ناقصا، فإنك تعيد إدخال أخطاء وقت التشغيل التي لن يراها نظام الأنواع أبدا. ومنذ TypeScript 5.5، تُستنتَج المسندات البسيطة تلقائيا في نداءات مثل filter، لكن الشرح الصريح يبقى القاعدة للدوال المُصدَّرة.

TYPESCRIPT
interface User { id: string; email: string }

function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    typeof value.id === "string" &&
    "email" in value &&
    typeof value.email === "string"
  );
}

const raw: unknown =
  JSON.parse('{"id":"1","email":"a@b.c"}');
if (isUser(raw)) {
  console.log(raw.email.toLowerCase());
}
مسند يتحقق من البيانات الخارجية قبل استعمالها.

اختبار المعرفة

تأكّد من أنك تذكّرت النقاط الأساسية في هذا الدرس.

  1. لماذا لا يكفي فحص typeof value === "object" للتضييق إلى كائن غير null؟
    • لأن typeof يُعيد "Object" بحرف كبير
    • لأن typeof null هو أيضا "object" في JavaScript
    • لأن typeof لا يعمل على الاتحادات
    • لأن المصفوفات تُعيد "array"
  2. أي شرح لنوع العائد يحوّل دالة إلى حارس نوع مخصص؟
    • boolean
    • value as User
    • value is User
    • asserts value
  3. ما الخطر الرئيسي لحارس نوع مخصص سيئ الكتابة؟
    • خطأ وقت التصريف داخل الدالة
    • تضييق كاذب: يصدّق المُصرّف نوعا يكون خاطئا وقت التشغيل
    • فقدان في أداء التصريف