Kodokon kodokon.com

أنماط الإنتاج: الأنواع الموسومة، satisfies، وملفّات .d.ts

طبِّق أنماط الخبراء التي تُؤمِّن قاعدة شفرة حقيقية: الاسمية المُحاكاة، والتحقّق دون توسيع، والتصريحات المحيطة.

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

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

تصنيف TypeScript بنيوي (structural): نوعان لهما الشكل نفسه قابلان للتبادل، فسلسلة string تُمثِّل مُعرِّف مستخدم لا تُميَّز عن أي string آخر. تُحاكي الوسْم (branding) التصنيف الاسمي بتقاطع النوع الأساسي مع خاصّية شبحية مفتاحها unique symbol: لا تحملها أي قيمة حقيقية إطلاقاً، لكن المُصرِّف صار الآن يُميّز كل وسْم عن الآخر.

TYPESCRIPT
declare const brand: unique symbol;

type Brand<T, Name extends string> =
  T & { readonly [brand]: Name };

type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;

function asUserId(raw: string): UserId {
  return raw as UserId;
}

declare function loadUser(id: UserId): void;

loadUser(asUserId("u_42")); // OK
// loadUser("u_42"); // error: bare string rejected
اسمية مُحاكاة، بكلفة وقت تشغيل صفرية.

يسدّ العامل satisfies (TypeScript 4.9) ثغرة دقيقة. التأشير : T يُوسِّع نوع المتغيّر إلى T ويفقد القيم الحرفية المُستنتَجة؛ وas T يُعطِّل جزءاً من الفحوص. أمّا satisfies T فيتحقّق من مطابقة التعبير لـT دون المساس بالنوع المُستنتَج، الذي يبقى بأقصى دقّة ممكنة. إنها الأداة المثلى لكائنات الإعداد: تحقّق كامل ودقّة سليمة.

TYPESCRIPT
type RouteDef = { path: string; auth?: boolean };

const routes = {
  home: { path: "/" },
  admin: { path: "/admin", auth: true },
} satisfies Record<string, RouteDef>;

routes.admin.auth;
// literal type true: the inferred precision is
// preserved, and an unknown key would be rejected
satisfies: فحص صارم دون توسيع.

يحتوي ملفّ .d.ts تصريحات محيطة (ambient) حصراً: فهو يصف قيماً ستوجد وقت التشغيل، دون إصدار سطر واحد من الجافاسكريبت. ولتوسيع النطاق العام من داخل وحدة (module)، تكون الكتلة declare global إلزامية - ولا تسمح بها المواصفات إلا في ملفّ يكون فعلاً وحدة، ومن هنا اصطلاح export {} في رأس الملفّ.

TYPESCRIPT
export {};

declare global {
  interface Window {
    analytics: { track(event: string): void };
  }
}
env.d.ts: العبارة export {} تحوّل الملفّ إلى وحدة.

بالنسبة إلى تبعية جافاسكريبت بلا أنواع منشورة، يُنشئ declare module وحدة محيطة: سيستخدم المُصرِّف تصريحك في كل استيراد للحزمة. انتبِه لنطاق skipLibCheck: عند تفعيله، يتجاهل أخطاء جميع ملفّات .d.ts، بما فيها ملفّاتك؛ لذا تحقّق من تصريحاتك عبر اختبارات أنواع موضوعة في ملفّات .ts عادية.

TYPESCRIPT
declare module "legacy-lib" {
  export interface InitOptions {
    debug?: boolean;
  }
  export function init(options?: InitOptions): void;
}
legacy-lib.d.ts: تصنيف حزمة بلا أنواع منشورة.

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

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

  1. ما الفرق بين satisfies T والتأشير : T؟
    • satisfies يُوسِّع نوع التعبير إلى T
    • satisfies يتحقّق من المطابقة لـT مع الحفاظ على النوع المُستنتَج الأكثر دقّة
    • satisfies يُعطِّل الفحوص، مثل as
    • لا فرق: الصيغتان متكافئتان
  2. وقت التشغيل، ماذا تحتوي قيمة موسومة مبنية بـBrand<string, ...>؟
    • سلسلة مصحوبة بخاصّية symbol
    • سلسلة عادية: الوسْم لا يوجد إلا وقت التصريف
    • كائناً يلفّ السلسلة الأصلية
  3. لماذا تُضيف export {} في رأس ملفّ .d.ts يستخدم declare global؟
    • لتصدير الأنواع العامّة إلى ملفّات أخرى
    • لتحويل الملفّ إلى وحدة، وهو شرط تتطلّبه declare global
    • لتفعيل الوضع الصارم تلقائياً
    • إنها زخرفية بحتة