لغة TypeScript

ملفات الإعلانات وتعريفات الأنواع

26 دقيقة الدرس 18 من 40

ملفات الإعلانات وتعريفات الأنواع في TypeScript

ملفات الإعلانات (.d.ts) هي جزء أساسي من نظام TypeScript البيئي. توفر معلومات النوع لكود JavaScript، مما يتيح فحص الأنواع و IntelliSense للمكتبات التي لا تحتوي على تطبيقات TypeScript الخاصة بها. في هذا الدرس، سنستكشف كيفية العمل مع ملفات الإعلانات وإنشاء تعريفات الأنواع الخاصة بك.

فهم ملفات الإعلانات

يصف ملف الإعلان شكل وحدة أو مكتبة JavaScript موجودة دون احتواء التطبيق الفعلي. ملفات الإعلانات لها امتداد .d.ts وتحتوي فقط على معلومات النوع.

البنية الأساسية:
<// math-utils.d.ts
export function add(a: number, b: number): number;
export function subtract(a: number, b: number): number;
export function multiply(a: number, b: number): number;

export interface CalculatorOptions {
  precision?: number;
  roundingMode?: 'floor' | 'ceil' | 'round';
}

export class Calculator {
  constructor(options?: CalculatorOptions);
  calculate(expression: string): number;
  clear(): void;
}

export const PI: number;
export const E: number;
>
ملاحظة: ملفات الإعلانات لا تحتوي على كود تطبيق—فقط توقيعات الأنواع والواجهات وتعريفات الأنواع.

مستودع DefinitelyTyped

DefinitelyTyped هو مستودع ضخم يحركه المجتمع لتعريفات الأنواع لآلاف مكتبات JavaScript. يتم نشر هذه التعريفات على npm تحت نطاق @types.

تثبيت تعريفات الأنواع:
<# تثبيت تعريفات الأنواع لمكتبة
npm install --save-dev @types/lodash
npm install --save-dev @types/express
npm install --save-dev @types/node
npm install --save-dev @types/react

# التحقق من الأنواع المتاحة
npm search @types/library-name

# يتم اكتشاف الأنواع تلقائيًا بواسطة TypeScript
# في node_modules/@types/
>
نصيحة: تحقق دائمًا من وجود تعريفات الأنواع قبل كتابة تعريفاتك الخاصة. استخدم npm search @types/package-name أو قم بزيارة TypeSearch.

كتابة الإعلانات المحيطة

تصف الإعلانات المحيطة الأنواع الموجودة في مكان آخر، عادةً في كود JavaScript أو المكتبات الخارجية. تستخدم الكلمة المفتاحية declare.

الإعلانات المحيطة:
<// global.d.ts - الإعلانات المحيطة للمتغيرات العامة

// الإعلان عن متغير عام
declare const API_URL: string;
declare const VERSION: string;

// الإعلان عن دالة عامة
declare function gtag(
  command: 'config' | 'event',
  targetId: string,
  config?: object
): void;

// الإعلان عن فئة عامة
declare class jQuery {
  constructor(selector: string);
  addClass(className: string): this;
  removeClass(className: string): this;
  on(event: string, handler: Function): this;
}

// الإعلان عن مساحة اسم عامة
declare namespace NodeJS {
  interface ProcessEnv {
    NODE_ENV: 'development' | 'production' | 'test';
    DATABASE_URL: string;
    API_KEY: string;
  }
}

// الآن يمكن استخدام هذه دون أخطاء
console.log(API_URL);
gtag('event', 'page_view');
const $el = new jQuery('#app');
const env = process.env.NODE_ENV;
>

إعلانات الوحدات

عند العمل مع وحدات JavaScript التي لا تحتوي على تعريفات أنواع، يمكنك الإعلان عن أنواعها باستخدام إعلانات الوحدات.

إعلان الوحدة:
<// declarations.d.ts

// الإعلان عن أنواع لوحدة طرف ثالث
declare module 'legacy-library' {
  export function processData(data: string): any;
  export class DataProcessor {
    constructor(options?: object);
    process(input: string): string;
  }
}

// الإعلان عن وحدة حرف بدل لاستيرادات الملفات
declare module '*.css' {
  const content: { [className: string]: string };
  export default content;
}

declare module '*.png' {
  const value: string;
  export default value;
}

declare module '*.svg' {
  import React = require('react');
  export const ReactComponent: React.FC<React.SVGProps<SVGSVGElement>>;
  const src: string;
  export default src;
}

// الإعلان عن وحدات JSON
declare module '*.json' {
  const value: any;
  export default value;
}

// الآن يمكنك استيراد هذه الملفات
import styles from './styles.css';
import logo from './logo.png';
import config from './config.json';
>
تحذير: استخدام إعلانات الوحدات بحرف البدل مع any يعطل فحص الأنواع. قدم أنواعًا محددة عندما يكون ذلك ممكنًا.

إنشاء ملفات الإعلانات لمكتبتك

عند نشر مكتبة TypeScript، يجب عليك إنشاء ملفات الإعلانات حتى يتمكن المستهلكون من الاستفادة من فحص الأنواع.

تكوين tsconfig.json:
<{
  "compilerOptions": {
    "declaration": true,        // إنشاء ملفات .d.ts
    "declarationMap": true,     // إنشاء خرائط المصدر لـ .d.ts
    "emitDeclarationOnly": false, // أيضًا إصدار ملفات JS
    "outDir": "./dist",         // دليل الإخراج
    "declarationDir": "./dist/types" // اختياري: دليل منفصل لـ .d.ts
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "**/*.test.ts"]
}
>
مثال على مصدر المكتبة:
<// src/index.ts
export interface UserConfig {
  apiUrl: string;
  timeout?: number;
  retries?: number;
}

export class ApiClient {
  constructor(config: UserConfig) {
    // التنفيذ
  }

  async get<T>(endpoint: string): Promise<T> {
    // التنفيذ
    return {} as T;
  }

  async post<T>(endpoint: string, data: any): Promise<T> {
    // التنفيذ
    return {} as T;
  }
}

export function createClient(config: UserConfig): ApiClient {
  return new ApiClient(config);
}

// بعد التجميع، ينشئ TypeScript:
// dist/index.js (التنفيذ)
// dist/index.d.ts (إعلانات الأنواع)
>
ملف الإعلان المُنشأ:
<// dist/index.d.ts (مُنشأ تلقائيًا)
export interface UserConfig {
  apiUrl: string;
  timeout?: number;
  retries?: number;
}

export declare class ApiClient {
  constructor(config: UserConfig);
  get<T>(endpoint: string): Promise<T>;
  post<T>(endpoint: string, data: any): Promise<T>;
}

export declare function createClient(config: UserConfig): ApiClient;
>

تكوين نوع Package.json

قم بتكوين package.json للإشارة إلى تعريفات الأنواع الصحيحة لمستهلكي حزمة npm.

package.json:
<{
  "name": "my-library",
  "version": "1.0.0",
  "main": "./dist/index.js",           // نقطة الدخول لـ CommonJS
  "module": "./dist/index.esm.js",     // نقطة الدخول لوحدات ES
  "types": "./dist/index.d.ts",        // إدخال تعريفات الأنواع
  "typings": "./dist/index.d.ts",      // بديل لـ "types"
  "exports": {
    ".": {
      "import": "./dist/index.esm.js",
      "require": "./dist/index.js",
      "types": "./dist/index.d.ts"
    },
    "./utils": {
      "import": "./dist/utils.esm.js",
      "require": "./dist/utils.js",
      "types": "./dist/utils.d.ts"
    }
  },
  "files": [
    "dist"
  ]
}
>
أفضل ممارسة: استخدم حقل "exports" لتكوين نقاط دخول الحزمة الحديثة مع دعم الأنواع المناسب.

توجيهات الشرطة الثلاثية

توجيهات الشرطة الثلاثية هي تعليقات من سطر واحد توفر تعليمات المترجم وهي صالحة فقط في أعلى الملف.

توجيهات الشرطة الثلاثية:
</// <reference path="./custom-types.d.ts" />
/// <reference types="node" />
/// <reference lib="es2020" />

// الإشارة إلى ملف إعلان آخر
/// <reference path="./globals.d.ts" />

// الإشارة إلى حزمة @types
/// <reference types="jquery" />

// الإشارة إلى مكتبة TypeScript
/// <reference lib="dom" />
/// <reference lib="es2021" />

// تخبر هذه التوجيهات TypeScript بتضمين
// تعريفات أنواع محددة أثناء التجميع
>
ملاحظة: توجيهات الشرطة الثلاثية هي في الغالب قديمة. يفضل TypeScript الحديث تكوين tsconfig.json واستيرادات ES6.

توسيع الأنواع الموجودة

يمكنك توسيع الأنواع الموجودة من المكتبات باستخدام دمج الإعلانات. هذا مفيد لإضافة خصائص أو طرق مخصصة.

توسيع الأنواع:
<// custom.d.ts

// توسيع نوع Express Request
import { Express } from 'express';

declare global {
  namespace Express {
    interface Request {
      user?: {
        id: string;
        email: string;
        role: 'admin' | 'user';
      };
      requestId: string;
    }
  }
}

// توسيع واجهة Window
interface Window {
  gtag: (command: string, ...args: any[]) => void;
  dataLayer: any[];
  myCustomProperty: string;
}

// توسيع نموذج Array الأولي
interface Array<T> {
  first(): T | undefined;
  last(): T | undefined;
}

// الآن يمكنك استخدام هذه الأنواع الموسعة
// في كودك دون أخطاء

// وسيط Express
app.use((req, res, next) => {
  req.requestId = generateId(); // لا يوجد خطأ
  if (req.user) {
    console.log(req.user.role); // لا يوجد خطأ
  }
  next();
});

// كود المتصفح
window.gtag('event', 'page_view'); // لا يوجد خطأ
console.log(window.myCustomProperty); // لا يوجد خطأ

// امتدادات المصفوفة
const arr = [1, 2, 3];
arr.first(); // لا يوجد خطأ
arr.last();  // لا يوجد خطأ
>
تحذير: كن حذرًا عند توسيع الأنواع العامة. يمكن أن يؤدي إلى تعارضات إذا قامت مكتبات متعددة بتوسيع نفس الأنواع.

إعلانات مساحة الاسم

مساحات الأسماء (تسمى سابقًا "الوحدات الداخلية") تنظم الكود وتمنع تلوث مساحة الاسم العامة في ملفات الإعلانات.

إعلان مساحة الاسم:
<// my-library.d.ts

declare namespace MyLibrary {
  // الواجهات
  interface Config {
    apiKey: string;
    debug?: boolean;
  }

  interface User {
    id: string;
    name: string;
  }

  // الفئات
  class Client {
    constructor(config: Config);
    getUser(id: string): Promise<User>;
  }

  // الدوال
  function init(config: Config): Client;
  function version(): string;

  // مساحة اسم متداخلة
  namespace Utils {
    function formatDate(date: Date): string;
    function parseJSON<T>(json: string): T;
  }

  // الثوابت
  const VERSION: string;
}

// الاستخدام
const client = MyLibrary.init({ apiKey: 'xyz' });
const user = await client.getUser('123');
const formatted = MyLibrary.Utils.formatDate(new Date());
console.log(MyLibrary.VERSION);
>

مثال عملي: إنشاء إعلان مكون إضافي

إعلان مكون إضافي كامل:
<// types/jquery-plugin.d.ts

// توسيع jQuery مع مكون إضافي مخصص
interface JQuery {
  /**
   * مكون إضافي لتلميحات الأدوات المخصص
   * @param options - خيارات التكوين
   */
  myTooltip(options?: MyTooltip.Options): JQuery;

  /**
   * عرض تلميح الأداة
   */
  myTooltip(action: 'show'): JQuery;

  /**
   * إخفاء تلميح الأداة
   */
  myTooltip(action: 'hide'): JQuery;

  /**
   * تدمير تلميح الأداة
   */
  myTooltip(action: 'destroy'): JQuery;
}

// الإعلان عن مساحة اسم المكون الإضافي
declare namespace MyTooltip {
  interface Options {
    content?: string;
    placement?: 'top' | 'bottom' | 'left' | 'right';
    trigger?: 'hover' | 'click' | 'manual';
    delay?: number;
    animation?: boolean;
    template?: string;
    onShow?: () => void;
    onHide?: () => void;
  }

  interface API {
    show(): void;
    hide(): void;
    toggle(): void;
    destroy(): void;
    update(content: string): void;
  }

  const defaults: Options;
  const version: string;
}

// أمثلة الاستخدام
$('#element').myTooltip({
  content: 'مرحبًا بالعالم',
  placement: 'top',
  trigger: 'hover'
});

$('#element').myTooltip('show');
$('#element').myTooltip('hide');

console.log(MyTooltip.version);
>

أنماط الإعلانات الشائعة

أنماط إعلانات متنوعة:
<// 1. تحميل الدالة الزائد
declare function createElement(tag: 'div'): HTMLDivElement;
declare function createElement(tag: 'span'): HTMLSpanElement;
declare function createElement(tag: string): HTMLElement;

// 2. واجهات قابلة للاستدعاء
interface ClickHandler {
  (event: MouseEvent): void;
  namespace: string;
  version: string;
}

declare const onClick: ClickHandler;

// 3. توقيعات المنشئ
interface UserConstructor {
  new (name: string): User;
  new (name: string, email: string): User;
  readonly prototype: User;
}

declare const User: UserConstructor;

// 4. أنواع هجينة (قابلة للاستدعاء والإنشاء)
interface JQueryStatic {
  (selector: string): JQuery;
  ajax(url: string, settings?: any): any;
  version: string;
}

declare const $: JQueryStatic;

// 5. قيود عامة في الإعلانات
declare function map<T, U>(
  array: T[],
  fn: (item: T, index: number) => U
): U[];

// 6. أنواع شرطية في الإعلانات
declare function process<T>(
  value: T
): T extends string ? string : number;
>
تمرين: أنشئ ملف إعلان لمكتبة تحليلات خيالية مع:
  1. كائن analytics عام مع طرق track و identify
  2. تعريفات الأنواع لخصائص الأحداث وسمات المستخدم
  3. أحمال طرق زائدة لمجموعات معاملات مختلفة
  4. مساحة اسم لخيارات التكوين

اختبار إعلاناتك

اختبار الإعلان:
<// test-types.ts - اختبر إعلانات أنواعك

import { ApiClient, UserConfig } from './index';

// تأكيدات النوع للتحقق من الأنواع
const config: UserConfig = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
};

const client = new ApiClient(config);

// يجب أن يُجمع هذا بدون أخطاء
(async () => {
  const user = await client.get<{ id: string }>('/user');
  const id: string = user.id; // يجب أن يكون string

  await client.post('/user', { name: 'جون' });
})();

// استخدم dtslint أو tsd للاختبار الآلي للإعلانات
// npm install --save-dev dtslint
// npm install --save-dev tsd
>
أفضل ممارسة: استخدم أدوات مثل tsd أو dtslint لكتابة اختبارات لإعلانات أنواعك، لضمان عملها كما هو متوقع.
ملخص: ملفات الإعلانات ضرورية لتوفير الأمان من حيث النوع في مشاريع TypeScript. أتقن كتابة واستخدام ملفات .d.ts للعمل بفعالية مع مكتبات JavaScript ونشر الحزم الآمنة من حيث النوع.