TypeScript: لغة TypeScript ومعالجة DOM

آخر تحديث: 2026-08-26

في مجال تطوير المتصفحات، يُعد التعامل مع نموذج الوثيقة (DOM) السيناريو الأكثر شيوعًا — وتشتمل لغة TypeScript على إعلانات أنواع مدمجة وشاملة لنموذج الوثيقة (DOM)، مما يوفر أمان الأنواع عند اختيار العناصر ومعالجة الأحداث والتعامل مع النماذج.

1. نظام أنواع DOM

(1) التسلسل الهرمي للوراثة في HTMLElement

TEXT 📖 للعرض فقط
EventTarget
  └─ Node
       └─ Element
            └─ HTMLElement
                 ├─ HTMLInputElement    (input Element)
                 ├─ HTMLButtonElement   (button Element)
                 ├─ HTMLDivElement      (div Element)
                 ├─ HTMLSpanElement     (span Element)
                 ├─ HTMLAnchorElement   (a Element)
                 ├─ HTMLCanvasElement   (canvas Element)
                 ├─ HTMLSelectElement   (select Element)
                 ├─ HTMLTextAreaElement (textarea Element)
                 └─ ... More Subclasses

(2) أنواع DOM الشائعة

النوع العنصر المقابل الخصائص الفريدة
HTMLElement جميع عناصر HTML className، style، innerHTML
HTMLInputElement <input> القيمة، النوع، محدد، العنصر النائب
HTMLButtonElement <button> معطل، النوع
HTMLSelectElement <select> القيمة، selectedIndex، الخيارات
HTMLTextAreaElement <textarea> القيمة، الصفوف، الأعمدة
HTMLCanvasElement <canvas> getContext()، العرض، الارتفاع
HTMLFormElement <form> العناصر، submit()، reset()
HTMLImageElement <img> src، alt، width، height

(3) أنواع استعلامات DOM

TYPESCRIPT
// getElementById Back HTMLElement | null
let el = document.getElementById("app");

// querySelector Back Element | null
let first = document.querySelector(".item");

// querySelectorAll Back NodeListOf<Element>
let all = document.querySelectorAll(".item");


2. استعلامات DOM وتضييق النطاق

(1) فحوصات القيم الفارغة

TYPESCRIPT
let el = document.getElementById("app");

// ❌ Use as is——It could be null
// el.innerHTML = "Hello";

// ✅ Method 1:if Inspection
if (el) {
  el.innerHTML = "Hello";
}

// ✅ Method 2:Non-empty assertion(When an element is found)
el!.innerHTML = "Hello";

// ✅ Method 3:Return Early
if (!el) return;
el.innerHTML = "Hello";  // After that el Definitely not null

(2) تضييق نطاق أنواع عناصر محددة

TYPESCRIPT
let input = document.getElementById("email");

// input The type is HTMLElement | null——Cannot access value Properties
// input.value  ❌

// ✅ Method 1:instanceof narrow
if (input instanceof HTMLInputElement) {
  console.log(input.value);  // ✅ HTMLInputElement has value
}

// ✅ Method 2:Type Assertion
let emailInput = document.getElementById("email") as HTMLInputElement;
console.log(emailInput.value);  // ✅

// ✅ Method 3:Generic Queries(Top Recommendations)
let emailInput2 = document.querySelector<HTMLInputElement>("#email");
if (emailInput2) {
  console.log(emailInput2.value);  // ✅ Automatically inferred as HTMLInputElement
}

(3) querySelector العام

TYPESCRIPT
// Generic parameters specify the expected element type
let input = document.querySelector<HTMLInputElement>("input[type=email]");
let button = document.querySelector<HTMLButtonElement>("#submit");
let canvas = document.querySelector<HTMLCanvasElement>("canvas");
let form = document.querySelector<HTMLFormElement>("form");

if (input) {
  console.log(input.value);       // ✅ HTMLInputElement.value
}
if (canvas) {
  let ctx = canvas.getContext("2d");  // ✅ CanvasRenderingContext2D | null
}

▶ مثال: جمع بيانات النماذج بطريقة آمنة من حيث الأنواع

TYPESCRIPT
interface LoginForm {
  email: string;
  password: string;
  remember: boolean;
}

function getFormData(formEl: HTMLFormElement): LoginForm {
  let email = formEl.querySelector<HTMLInputElement>('input[name="email"]');
  let password = formEl.querySelector<HTMLInputElement>('input[name="password"]');
  let remember = formEl.querySelector<HTMLInputElement>('input[name="remember"]');

  return {
    email: email?.value ?? "",
    password: password?.value ?? "",
    remember: remember?.checked ?? false
  };
}

// Usage
let form = document.querySelector<HTMLFormElement>("#login-form");
if (form) {
  let data = getFormData(form);
  console.log(`Email:${data.email}`);
  console.log(`Remember Me:${data.remember}`);
}
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
// Executed successfully


3. أنواع الأحداث

(1) أنواع الأحداث الشائعة

نوع الحدث سيناريو التشغيل السمات المحددة
MouseEvent النقر/حركة الماوس clientX، clientY، button
KeyboardEvent الإدخال من لوحة المفاتيح key، code، ctrlKey، shiftKey
InputEvent تغييرات الإدخال data, inputType
FocusEvent تغيير التركيز relatedTarget
SubmitEvent إرسال النموذج المرسل
ChangeEvent تغيير القيمة الهدف
ClipboardEvent الحافظة clipboardData
DragEvent السحب نقل البيانات
WheelEvent أسطوانة دلتاY، دلتاX
TouchEvent اللمس المفاتيح

(2) أنواع مستمعي الأحداث

TYPESCRIPT
// addEventListener The type is automatically inferred from the event name
const button = document.querySelector("button");

button?.addEventListener("click", (event) => {
  // event Automatically inferred as MouseEvent
  console.log(event.clientX, event.clientY);  // ✅
});

document.addEventListener("keydown", (event) => {
  // event Automatically inferred as KeyboardEvent
  console.log(event.key);     // ✅
  console.log(event.ctrlKey); // ✅
  if (event.key === "Enter" && event.ctrlKey) {
    console.log("Ctrl+Enter Press");
  }
});

(3) تضييق نطاق أنواع أهداف الفعاليات

TYPESCRIPT
function handleInput(event: Event) {
  let target = event.target;

  // target The type is EventTarget | null——Needs to be narrowed
  if (target instanceof HTMLInputElement) {
    console.log(target.value);    // ✅ HTMLInputElement.value
    console.log(target.type);     // ✅ HTMLInputElement.type
  }
}

// Abbreviation——Generic Event Handling
function onInput(event: InputEvent) {
  let target = event.target as HTMLInputElement;
  console.log(target.value);  // ✅
}


4. أمان الأنواع في عمليات النماذج

(1) التكرار عبر عناصر النموذج

TYPESCRIPT
function collectForm(form: HTMLFormElement): Record<string, string | boolean> {
  let data: Record<string, string | boolean> = {};

  for (let element of Array.from(form.elements)) {
    if (element instanceof HTMLInputElement) {
      if (element.type === "checkbox") {
        data[element.name] = element.checked;
      } else {
        data[element.name] = element.value;
      }
    } else if (element instanceof HTMLSelectElement) {
      data[element.name] = element.value;
    } else if (element instanceof HTMLTextAreaElement) {
      data[element.name] = element.value;
    }
  }

  return data;
}

(2) التحقق من صحة النموذج

TYPESCRIPT
interface ValidationRule {
  required?: boolean;
  minLength?: number;
  maxLength?: number;
  pattern?: RegExp;
  message: string;
}

function validateInput(
  input: HTMLInputElement,
  rules: ValidationRule[]
): string | null {
  let value = input.value;

  for (let rule of rules) {
    if (rule.required && !value.trim()) {
      return rule.message;
    }
    if (rule.minLength && value.length < rule.minLength) {
      return rule.message;
    }
    if (rule.maxLength && value.length > rule.maxLength) {
      return rule.message;
    }
    if (rule.pattern && !rule.pattern.test(value)) {
      return rule.message;
    }
  }

  return null;  // Verification Passed
}

// Usage
let email = document.querySelector<HTMLInputElement>('input[name="email"]');
if (email) {
  let error = validateInput(email, [
    { required: true, message: "The email address cannot be left blank." },
    { pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: "The email address format is incorrect." }
  ]);
  if (error) {
    console.log(error);
  }
}


5. الأحداث المخصصة وتمرير البيانات

(1) CustomEvent

TYPESCRIPT
// Create a Custom Event and Pass Data
interface CartItemAddedDetail {
  itemId: number;
  quantity: number;
  price: number;
}

let event = new CustomEvent<CartItemAddedDetail>("cart:itemadded", {
  detail: { itemId: 1, quantity: 2, price: 99.9 },
  bubbles: true
});

document.dispatchEvent(event);

(2) الاستماع إلى الأحداث المخصصة

TYPESCRIPT
document.addEventListener("cart:itemadded", (event) => {
  // event Type automatically inferred as CustomEvent<CartItemAddedDetail>
  console.log(event.detail.itemId);     // ✅ number
  console.log(event.detail.quantity);   // ✅ number
  console.log(event.detail.price);      // ✅ number
});

(3) توسيع واجهة Document

إذا لم يقم TypeScript باستنتاج نوع حدث مخصص تلقائيًا، فيمكنك توسيع واجهة Document:

TYPESCRIPT
interface CartEventMap {
  "cart:itemadded": CustomEvent<CartItemAddedDetail>;
  "cart:itemremoved": CustomEvent<{ itemId: number }>;
}

declare global {
  interface Document {
    addEventListener<K extends keyof CartEventMap>(
      type: K,
      listener: (this: Document, ev: CartEventMap[K]) => void
    ): void;
  }
}

▶ مثال: معالجة أحداث لوحة المفاتيح

TYPESCRIPT
function setupKeyboardShortcuts(): void {
  document.addEventListener("keydown", (event: KeyboardEvent) => {
    if (event.ctrlKey && event.key === "s") {
      event.preventDefault();
      console.log("Save triggered");
    }
    if (event.key === "Escape") {
      let modal = document.querySelector<HTMLDialogElement>("dialog[open]");
      modal?.close();
    }
  });
}

setupKeyboardShortcuts();
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
Save triggered

▶ مثال: العرض على Canvas بطريقة آمنة من حيث النوع

TYPESCRIPT
function drawCircle(
  canvas: HTMLCanvasElement,
  x: number, y: number, radius: number, color: string
): void {
  let ctx = canvas.getContext("2d");
  if (!ctx) return;

  ctx.fillStyle = color;
  ctx.beginPath();
  ctx.arc(x, y, radius, 0, Math.PI * 2);
  ctx.fill();
}

let canvas = document.querySelector<HTMLCanvasElement>("#myCanvas");
if (canvas) {
  drawCircle(canvas, 100, 100, 50, "blue");
}
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
No runtime output — demonstrates type-safe canvas rendering

❓ أسئلة شائعة

س أيهما أفضل، التأكيد العام querySelector أم as؟
ج التأكيد العام querySelector هو الأفضل—document.querySelector<HTMLInputElement>("#email") فهو يحدد النوع عند إجراء الاستعلام، وعند استخدامه مع فحص القيمة الفارغة، يضمن الاستخدام الآمن. أما تعبير as فيتخطى فحص القيمة الفارغة — document.getElementById("email") as HTMLInputElement فإذا كان العنصر غير موجود، فسوف يتعطل الكود اللاحق أثناء وقت التشغيل.
س ما الفرق بين event.target وevent.currentTarget؟
ج event.target هو العنصر الذي أطلق الحدث فعليًّا (والذي قد يكون عنصرًا تابعًا)، بينما event.currentTarget هو العنصر المرتبط بمستمع الحدث (العنصر المشار إليه بـ this). قد يختلف هذان العنصران أثناء انتشار الحدث. ومن الأفضل عمومًا استخدام event.currentTarget — فهو العنصر الذي قمت بربط مستمع الحدث به.
س هل تؤثر عمليات DOM بشكل كبير على الأداء؟
ج لا تؤثر تعليقات الأنواع على الأداء على الإطلاق (حيث يتم حذفها في مرحلة التحويل البرمجي). أما أداء عمليات DOM نفسها فيعتمد على منطق الكود — فقد تؤدي عمليات القراءة والكتابة المتكررة على DOM إلى حدوث عمليات إعادة ترتيب العناصر وإعادة رسمها. يساعدك نظام الأنواع في TypeScript على كتابة كود DOM أكثر أمانًا، لكنه لا يؤثر على أداء عمليات DOM أثناء التشغيل.
س هل توجد أنواع DOM في البيئات غير المتعلقة بالمتصفح؟
ج يجب أن يتضمن lib "DOM". لا يتضمن Node.js أنواع DOM بشكل افتراضي — إذا تم تكوين ملف tsconfig بـ "ES2020" فقط دون "DOM"، فستؤدي المتغيرات العالمية مثل document و window إلى حدوث أخطاء. أنواع DOM ليست مطلوبة لمشاريع الخلفية في Node.js، ولكن يجب تضمينها في مشاريع الواجهة الأمامية.

📖 ملخص

📝 تمارين

  1. المشكلة الأساسية (صعوبة ⭐): باستخدام TypeScript، حدد عنصر input وعنصر button، ثم أضف مستمعًا لحدث النقر إلى الزر بحيث يتم عرض قيمة input عند النقر عليه.
  2. مشكلة متقدمة (درجة الصعوبة ⭐⭐): اكتب دالة validateForm(form: HTMLFormElement): { valid: boolean; errors: Record<string, string> } تقوم بالتنقل عبر عناصر النموذج، وتتحقق مما إذا كانت حقول الإدخال required فارغة، وتُرجع نتيجة التحقق من الصحة.
  3. التحدي (الصعوبة: ⭐⭐⭐): قم بتنفيذ ناقل أحداث آمن من حيث النوع — EventBus.on<K extends keyof EventMap>(event: K, handler: (data: EventMap[K]) => void)، EventBus.emit<K>(event: K, data: EventMap[K]). حدد EventMap الذي يحتوي على "user:login" (بيانات من النوع User) و"cart:update" (بيانات من النوع Cart)، وتحقق من أمان الأنواع.
Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%