TypeScript: حراس الأنواع وتضييق النطاق في TypeScript

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

يُعد تضييق النوع إحدى الميزات الأكثر فائدة في TypeScript — فهو يضيق نطاق الأنواع الواسعة ليصبح أكثر دقة، مما يتيح لك الوصول بأمان إلى الخصائص والطرق الخاصة بأنواع محددة.

1. نظرة عامة على تضييق النوع

لا يمكن للمتغيرات من نوع الاتحاد الوصول إلا إلى الخصائص المشتركة بين جميع العناصر. ويتيح لنا تضييق النوع «تضييق» النوع في فرع معين من الكود للوصول إلى العناصر الخاصة بذلك الفرع حصريًّا:

TYPESCRIPT
function process(value: string | number) {
  // Before narrowing——value Only shared methods can be used
  console.log(value.toString());  // ✅ string and number All of them

  // After narrowing——value You can use a specific method
  if (typeof value === "string") {
    console.log(value.toUpperCase());  // ✅ string Unique Approach
  } else {
    console.log(value.toFixed(2));     // ✅ number Unique Approach
  }
}

يدعم TypeScript أنواع التضييق التالية:

طريقة التضييق السيناريوهات القابلة للتطبيق
typeof التحقق من النوع الأساسي
instanceof التحقق من مثيل الفئة
in عامل هل هذه الخاصية موجودة؟
فحص التكافؤ مقارنة ===/!==
حراس الأنواع المخصصة فحوصات الأنواع المعقدة
قابل للتضييق التضييق تلقائيًا أثناء التخصيص


2. تضييق نطاق نوع (typeof)

typeof تُستخدم في الغالب للتمييز بين الأنواع الأساسية:

(1) الاستخدام الأساسي

TYPESCRIPT
function padLeft(value: string, padding: string | number): string {
  if (typeof padding === "number") {
    return " ".repeat(padding) + value;    // padding narrowed to number
  }
  return padding + value;                   // padding narrowed to string
}

console.log(padLeft("hello", 4));      // "    hello"
console.log(padLeft("hello", ">>>"));  // ">>>hello"

(2) قيمة الإرجاع لـ typeof

TYPESCRIPT
typeof "hello"     // "string"
typeof 42          // "number"
typeof true        // "boolean"
typeof undefined   // "undefined"
typeof Symbol()    // "symbol"
typeof 100n        // "bigint"
typeof {}          // "object"   ⚠️ Including null, Array, Date, etc.
typeof function(){} // "function"
🔥 خطأ شائع: typeof null === "object" هو خطأ قديم في لغة جافا سكريبت. لا يمكنك استخدام typeof للتمييز بين null والكائنات الأخرى — بل عليك استخدام === null للتحقق من ذلك.

(3) استخدام typeof مع switch

TYPESCRIPT
function describe(value: string | number | boolean | undefined) {
  switch (typeof value) {
    case "string":
      return `String:${value.toUpperCase()}`;
    case "number":
      return `Numbers:${value.toFixed(2)}`;
    case "boolean":
      return `Boolean:${value}`;
    case "undefined":
      return "Undefined";
  }
}


3. تضييق نطاق instanceof

instanceof التحقق مما إذا كان الكائن مثيلًا لفئة معينة — مناسب لتضييق نطاق أنواع المراجع:

(1) الاستخدام الأساسي

TYPESCRIPT
function formatValue(value: Date | string | Error): string {
  if (value instanceof Date) {
    return value.toISOString();         // Date Methods
  } else if (value instanceof Error) {
    return value.message;               // Error Properties
  } else {
    return value.toUpperCase();         // string Methods
  }
}

console.log(formatValue(new Date()));          // "2024-..."
console.log(formatValue(new Error("Error")));   // "Error"
console.log(formatValue("hello"));             // "HELLO"

(2) قيود instanceof

instanceof لا يمكن استخدامها إلا مع مثيلات الفئات — وليس مع الواجهات أو الأسماء المستعارة للأنواع (التي لا توجد في وقت التشغيل):

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

// ❌ instanceof Cannot be used for interfaces
// if (pet instanceof Dog) { ... }

// ✅ Do you need to use a custom type guard or in Operator

(3) الفئات المخصصة وinstanceof

TYPESCRIPT
class NetworkError extends Error {
  constructor(public statusCode: number) {
    super(`Network Error:${statusCode}`);
  }
}

class ValidationError extends Error {
  constructor(public field: string) {
    super(`Validation Error:${field}`);
  }
}

function handleError(error: NetworkError | ValidationError): string {
  if (error instanceof NetworkError) {
    return `HTTP ${error.statusCode} Error`;
  } else {
    return `Field ${error.field} Invalid`;
  }
}


4. تضييق نطاق عامل in

in يتحقق مما إذا كان الكائن يحتوي على خاصية معينة — وهو أمر مفيد للتمييز بين الواجهات وأسماء الأنواع البديلة:

(1) الاستخدام الأساسي

TYPESCRIPT
interface Fish {
  swim(): void;
}

interface Bird {
  fly(): void;
}

function move(animal: Fish | Bird) {
  if ("swim" in animal) {
    animal.swim();   // ✅ Fish Type
  } else {
    animal.fly();    // ✅ Bird Type
  }
}

(2) السمات المميزة

TYPESCRIPT
interface Circle {
  kind: "circle";
  radius: number;
}

interface Square {
  kind: "square";
  sideLength: number;
}

type Shape = Circle | Square;

function getArea(shape: Shape): number {
  if (shape.kind === "circle") {
    return Math.PI * shape.radius ** 2;   // ✅ Circle 's radius
  } else {
    return shape.sideLength ** 2;          // ✅ Square 's sideLength
  }
}

▶ مثال: معالجة الأحداث الآمنة من حيث النوع

TYPESCRIPT
interface ClickEvent {
  type: "click";
  x: number;
  y: number;
}

interface KeyEvent {
  type: "keydown" | "keyup";
  key: string;
  ctrlKey: boolean;
}

interface ScrollEvent {
  type: "scroll";
  scrollTop: number;
  scrollLeft: number;
}

type UIEvent = ClickEvent | KeyEvent | ScrollEvent;

function handleEvent(event: UIEvent): string {
  switch (event.type) {
    case "click":
      return `Click here:(${event.x}, ${event.y})`;
    case "keydown":
    case "keyup":
      return `Button:${event.key},Ctrl:${event.ctrlKey}`;
    case "scroll":
      return `Scroll:top=${event.scrollTop}, left=${event.scrollLeft}`;
  }
}

console.log(handleEvent({ type: "click", x: 100, y: 200 }));
console.log(handleEvent({ type: "keydown", key: "Enter", ctrlKey: false }));
console.log(handleEvent({ type: "scroll", scrollTop: 50, scrollLeft: 0 }));
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
Click here:(100, 200)
Button:Enter,Ctrl:false
Scroll:top=50, left=0


5. حراس الأنواع المخصصة

عندما لا تكون طرق التضييق المدمجة كافية، يمكنك كتابة دوال حماية أنواع مخصصة:

(1) المسند النوعي

TYPESCRIPT
interface Dog {
  bark(): void;
  breed: string;
}

interface Cat {
  meow(): void;
  color: string;
}

// Type Predicates:The return value is "Parameter Name is Type"
function isDog(animal: Dog | Cat): animal is Dog {
  return "bark" in animal;
}

function interact(animal: Dog | Cat) {
  if (isDog(animal)) {
    animal.bark();    // ✅ narrowed to Dog
    console.log(animal.breed);
  } else {
    animal.meow();    // ✅ narrowed to Cat
    console.log(animal.color);
  }
}

(2) دالة التأكيد

تُطلق دوال التأكيد استثناءً عندما لا تتحقق الشرط — لتُبلغ TypeScript بأن «إذا تم تنفيذ هذا السطر، فيجب أن يكون الشرط صحيحًا»:

TYPESCRIPT
function assertDefined<T>(value: T | undefined | null, message?: string): asserts value is NonNullable<T> {
  if (value == null) {
    throw new Error(message ?? "The value cannot be null or undefined");
  }
}

function processUser(user: User | undefined) {
  assertDefined(user, "User does not exist");
  // After that user The type has been narrowed down to User(Excludes undefined)
  console.log(user.name.toUpperCase());  // ✅ Safety
}

(3) التأكيدات مقابل المسندات النوعية

الخاصية مسند النوع x is T دالة التأكيد asserts x is T
قيمة الإرجاع منطقية فارغة (تُطلق استثناءً في حالة عدم استيفاء الشرط)
الاستخدام if (isType(x)) assertIsType(x)
تضييق النطاق التضييق في فرع if التضييق التلقائي بعد الاستدعاء
السيناريوهات المناسبة معالجة الفروع بعد الفحص فحوصات الشروط المسبقة


6. تضييق نطاق المهمة

تقوم عمليات التعيين أيضًا بتضييق النوع:

TYPESCRIPT
let value: string | number;

value = "hello";
console.log(value.toUpperCase());  // ✅ After assignment, it narrows to string

value = 42;
console.log(value.toFixed(2));     // ✅ After assignment, it narrows to number

(1) تحليل تدفق التحكم

تتتبع TypeScript التغييرات التي تطرأ على نوع المتغير طوال مسار التدفق:

TYPESCRIPT
function example(x: string | number | boolean) {
  // x: string | number | boolean
  if (typeof x === "string") {
    // x: string
    console.log(x.toUpperCase());
  } else {
    // x: number | boolean
    if (typeof x === "number") {
      // x: number
      console.log(x.toFixed(2));
    } else {
      // x: boolean
      console.log(x);
    }
  }
}

(2) التضييق وإعادة التخصيص

TYPESCRIPT
let value: string | number;

value = "hello";
console.log(value.length);  // ✅ string

value = 42;
// console.log(value.length);  // ❌ number None length

value = true;  // ❌ boolean Not in a composite type


7. الفحص الشامل

تأكد من أن عبارة switch/if تشمل جميع الأنواع المحتملة — استخدم النوع never لضمان الاكتمال:

TYPESCRIPT
type Shape = "circle" | "square" | "triangle";

function getIcon(shape: Shape): string {
  switch (shape) {
    case "circle": return "○";
    case "square": return "□";
    case "triangle": return "△";
    default: {
      // If all case It's all taken care of.,shape Here is never
      const _exhaustive: never = shape;
      return _exhaustive;
    }
  }
}

// If in the future Shape Added "hexagon" But I didn't add it case
// default Branched _exhaustive Report Type Error
// This is a reminder to fill in any missing information. case

(1) فحص شامل أكثر إيجازًا

TYPESCRIPT
function assertNever(value: never): never {
  throw new Error(`Unprocessed values:${value}`);
}

type Action = "create" | "update" | "delete";

function handleAction(action: Action) {
  switch (action) {
    case "create": /* ... */ break;
    case "update": /* ... */ break;
    case "delete": /* ... */ break;
    default:
      assertNever(action);  // If omitted case,A type error will be reported here.
  }
}

▶ مثال: استخدام typeof وinstanceof لتضييق النطاق

TYPESCRIPT
function format(value: string | number | Date): string {
  if (typeof value === "string") {
    return value.trim().toUpperCase();
  } else if (typeof value === "number") {
    return value.toFixed(2);
  } else if (value instanceof Date) {
    return value.toISOString();
  }
  return String(value);
}

console.log(format("  hello  "));        // "HELLO"
console.log(format(3.14159));            // "3.14"
console.log(format(new Date("2024-01-01"))); // "2024-01-01T00:00:00.000Z"
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
HELLO
3.14
2024-01-01T00:00:00.000Z

▶ مثال: حارس نوع مخصص مع الاتحادات المميزة

TYPESCRIPT
interface Circle { kind: "circle"; radius: number; }
interface Rectangle { kind: "rectangle"; width: number; height: number; }
type Shape = Circle | Rectangle;

function isCircle(shape: Shape): shape is Circle {
  return shape.kind === "circle";
}

function area(shape: Shape): number {
  if (isCircle(shape)) {
    return Math.PI * shape.radius ** 2;   // Circle
  }
  return shape.width * shape.height;       // Rectangle
}

console.log(area({ kind: "circle", radius: 5 }));       // 78.54
console.log(area({ kind: "rectangle", width: 4, height: 6 })); // 24
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
78.53981633974483
24


❓ أسئلة شائعة

س ما الفرق بين typeof وinstanceof؟
ج تقوم typeof بالتحقق من «النوع البدائي» للقيمة (سلسلة/عدد/قيمة منطقية/غير محدد/كائن/دالة) وتُرجع سلسلة؛ وهي مناسبة للتحقق من الأنواع البدائية. أما instanceof فتتحقق مما إذا كانت القيمة مثيلًا لفئة معينة؛ وهي مناسبة للتحقق من الأنواع المرجعية (مثل Date وError والفئات المخصصة). وهما متكاملتان ولا تستبعد إحداهما الأخرى.
س ما هو تأثير حراس الأنواع المخصصة على الأداء؟
ج لا يوجد أي تأثير. تُستخدم حراس الأنواع فقط في مرحلة الترجمة — حيث يتألف كود جافا سكريبت المُترجم من عبارات if عادية وعمليات فحص للخصائص. ولا توجد المسندات النوعية (x is T) ودوال التأكيد (asserts x is T) في مرحلة التشغيل، لذا لا توجد أي أعباء إضافية.
س لماذا يسمح عامل in بتضييق النوع؟
ج لأن TypeScript تدرك أنه إذا كان للكائن خاصية معينة، فلا بد أنه ينتمي إلى واجهة تتضمن تلك الخاصية. عندما تكون قيمة "swim" in animal صحيحة، يجب أن يُنفذ animal واجهة تتضمن swim. وهذا استنتاج منطقي لا يتطلب معلومات عن النوع في وقت التشغيل.
س متى تحتاج إلى كتابة حراس أنواع مخصصين؟
ج عندما تعجز طرق تضييق الأنواع المدمجة (typeof، instanceof، in، ===) عن التمييز بين الأنواع. السيناريو الأكثر شيوعًا هو التمييز بين الواجهات — نظرًا لأن الواجهات لا توجد في وقت التشغيل، فلا يمكن التحقق منها باستخدام instanceof؛ وبدلاً من ذلك، يجب عليك استخدام in للتحقق من وجود خصائص قابلة للتحديد أو كتابة حراس أنواع مخصصين.

📖 ملخص

📝 تمارين

  1. المسألة الأساسية (الصعوبة ⭐): اكتب دالة doubleOrRepeat(value: string | number) — إذا كان value عددًا، فاضربه في 2؛ وإذا كان سلسلة أحرف، فقم بضمها إلى نفسها مرة واحدة (على سبيل المثال، "hi" → "hihi"). استخدم typeof لتقييد المدخلات.
  2. مشكلة متقدمة (درجة الصعوبة ⭐⭐): عرّف واجهتين، Admin (التي تحتوي على الطريقة hasPermission) وGuest (التي تحتوي على الطريقة requestAccess)، وقم بتحديدهما باستخدام السمة المميزة role. اكتب دالة تستدعي طرقًا مختلفة بناءً على الدور.
  3. التحدي (الصعوبة: ⭐⭐⭐): اكتب حارس نوع مخصص isNonNull<T>(value: T | null | undefined): value is NonNullable<T>، ثم استخدمه في الدالة filterNonNull<T>(arr: (T | null | undefined)[]): T[] لتصفية القيم الفارغة (فارغ) وغير المُعرَّفة (غير معرّف) وإرجاع مصفوفة غير فارغة.
Web-Tutorial.com

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

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

100%