طبِّق أنماط الخبراء التي تُؤمِّن قاعدة شفرة حقيقية: الاسمية المُحاكاة، والتحقّق دون توسيع، والتصريحات المحيطة.
افتح هذا الدرس في Kodokonتصنيف TypeScript بنيوي (structural): نوعان لهما الشكل نفسه قابلان للتبادل، فسلسلة string تُمثِّل مُعرِّف مستخدم لا تُميَّز عن أي string آخر. تُحاكي الوسْم (branding) التصنيف الاسمي بتقاطع النوع الأساسي مع خاصّية شبحية مفتاحها unique symbol: لا تحملها أي قيمة حقيقية إطلاقاً، لكن المُصرِّف صار الآن يُميّز كل وسْم عن الآخر.
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 دون المساس بالنوع المُستنتَج، الذي يبقى بأقصى دقّة ممكنة. إنها الأداة المثلى لكائنات الإعداد: تحقّق كامل ودقّة سليمة.
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يحتوي ملفّ .d.ts تصريحات محيطة (ambient) حصراً: فهو يصف قيماً ستوجد وقت التشغيل، دون إصدار سطر واحد من الجافاسكريبت. ولتوسيع النطاق العام من داخل وحدة (module)، تكون الكتلة declare global إلزامية - ولا تسمح بها المواصفات إلا في ملفّ يكون فعلاً وحدة، ومن هنا اصطلاح export {} في رأس الملفّ.
export {};
declare global {
interface Window {
analytics: { track(event: string): void };
}
}بالنسبة إلى تبعية جافاسكريبت بلا أنواع منشورة، يُنشئ declare module وحدة محيطة: سيستخدم المُصرِّف تصريحك في كل استيراد للحزمة. انتبِه لنطاق skipLibCheck: عند تفعيله، يتجاهل أخطاء جميع ملفّات .d.ts، بما فيها ملفّاتك؛ لذا تحقّق من تصريحاتك عبر اختبارات أنواع موضوعة في ملفّات .ts عادية.
declare module "legacy-lib" {
export interface InitOptions {
debug?: boolean;
}
export function init(options?: InitOptions): void;
}satisfies T والتأشير : T؟Brand<string, ...>؟export {} في رأس ملفّ .d.ts يستخدم declare global؟