لغة TypeScript

تأكيدات الأنواع وحراس الأنواع

27 دقيقة الدرس 10 من 40

تأكيدات الأنواع وحراس الأنواع

توفر TypeScript آليات قوية للعمل مع الأنواع في وقت التشغيل. تأكيدات الأنواع تسمح لك بإخبار المترجم "ثق بي، أعرف ما أفعله"، بينما حراس الأنواع تتيح لك تضييق الأنواع بأمان بناءً على فحوصات وقت التشغيل. فهم هذه المفاهيم ضروري لكتابة كود TypeScript مرن وآمن من حيث الأنواع.

تأكيدات الأنواع

تأكيدات الأنواع هي طريقة لإخبار TypeScript بأنك تعرف أكثر عن نوع القيمة مما يعرفه. إنها لا تقوم بأي فحوصات أو تحويلات في وقت التشغيل؛ إنها فقط للمترجم.

تأكيدات الأنواع مع الكلمة المفتاحية 'as':

// تأكيد النوع الأساسي
let someValue: unknown = 'Hello, TypeScript!';

// تأكيد أن someValue هي سلسلة
let strLength: number = (someValue as string).length;
console.log(strLength); // 18

// تأكيد النوع مع عناصر DOM
const inputElement = document.getElementById('username') as HTMLInputElement;
inputElement.value = 'john_doe';

// تأكيد النوع مع استجابة fetch
async function fetchUser(id: string): Promise<void> {
  const response = await fetch(`/api/users/${id}`);
  const data = await response.json();

  // تأكيد شكل البيانات
  const user = data as {
    id: string;
    name: string;
    email: string;
  };

  console.log(user.name);
}

// تأكيد النوع مع حروف الكائن
interface Point {
  x: number;
  y: number;
}

const point = { x: 10, y: 20, z: 30 } as Point;
// ملاحظة: يتم تجاهل خاصية 'z' بواسطة نظام الأنواع

// تأكيدات متعددة (سلسلة)
let value: unknown = '123';
let numValue = (value as unknown) as number; // غير موصى به، ولكنه ممكن
تحذير: تأكيدات الأنواع تتجاوز فحص الأنواع في TypeScript. استخدمها بحذر وفقط عندما تكون متأكداً من النوع. التأكيدات غير الصحيحة يمكن أن تؤدي إلى أخطاء في وقت التشغيل.

بناء جملة القوس الزاوي (بديل)

تدعم TypeScript أيضاً بناء جملة القوس الزاوي لتأكيدات الأنواع، على الرغم من أن as مفضل في ملفات JSX/TSX:

بناء جملة القوس الزاوي:

// بناء جملة القوس الزاوي (يعادل 'as')
let someValue: unknown = 'Hello!';
let strLength: number = (<string>someValue).length;

// كلا البنيتين متكافئتان
let value1 = someValue as string;
let value2 = <string>someValue;

// ومع ذلك، بناء جملة 'as' مطلوب في ملفات .tsx
// لأن <> يتعارض مع بناء جملة JSX
نصيحة: فضل بناء جملة as لتأكيدات الأنواع. إنه أكثر اتساقاً عبر ملفات TypeScript و TSX، وهو النهج الموصى به الحديث.

عامل تأكيد غير الفارغ

عامل تأكيد غير الفارغ (!) يخبر TypeScript أن القيمة ليست null أو undefined:

تأكيد غير الفارغ:

// دالة قد ترجع null
function findUser(id: string): { name: string; email: string } | null {
  // محاكاة البحث في قاعدة البيانات
  if (id === '123') {
    return { name: 'John', email: 'john@example.com' };
  }
  return null;
}

// استخدام تأكيد غير الفارغ
const user = findUser('123')!; // تأكيد أن النتيجة ليست null
console.log(user.name); // لا خطأ

// بدون تأكيد غير الفارغ
const user2 = findUser('123');
// console.log(user2.name); // خطأ: الكائن من المحتمل أن يكون 'null'

// تأكيد غير الفارغ مع التسلسل الاختياري
interface Config {
  apiKey?: string;
}

const config: Config = { apiKey: 'secret-123' };

// تأكيد أن apiKey موجود
const key: string = config.apiKey!;
console.log(key.toUpperCase());

// مثال عنصر DOM
const button = document.getElementById('submit-btn')!;
button.addEventListener('click', () => {
  console.log('Button clicked');
});
تحذير: عامل تأكيد غير الفارغ خطير. إذا كانت القيمة في الواقع null أو undefined، ستحصل على خطأ في وقت التشغيل. استخدمه فقط عندما تكون متأكداً تماماً من وجود القيمة.

حراس الأنواع - typeof

عامل typeof هو عامل JavaScript مدمج تستخدمه TypeScript لتضييق النوع:

حارس النوع typeof:

// typeof مع الأنواع البدائية
function processValue(value: string | number): string {
  if (typeof value === 'string') {
    // TypeScript تعرف أن value هي string هنا
    return value.toUpperCase();
  } else {
    // TypeScript تعرف أن value هي number هنا
    return value.toFixed(2);
  }
}

console.log(processValue('hello')); // "HELLO"
console.log(processValue(3.14159)); // "3.14"

// typeof مع أنواع متعددة
function printValue(value: string | number | boolean | undefined): void {
  if (typeof value === 'string') {
    console.log(`String: ${value}`);
  } else if (typeof value === 'number') {
    console.log(`Number: ${value}`);
  } else if (typeof value === 'boolean') {
    console.log(`Boolean: ${value}`);
  } else {
    console.log('Value is undefined');
  }
}

// قيود typeof
const arr = [1, 2, 3];
console.log(typeof arr); // "object" (ليس مفيداً للمصفوفات)

const obj = { x: 10 };
console.log(typeof obj); // "object"

const nullValue = null;
console.log(typeof nullValue); // "object" (خلل في JavaScript)
ملاحظة: typeof يعمل بشكل جيد مع الأنواع البدائية لكن لديه قيود مع الكائنات والمصفوفات وnull. لهذه الحالات، استخدم حراس أنواع أخرى.

حراس الأنواع - instanceof

عامل instanceof يتحقق مما إذا كان الكائن هو نسخة من فئة معينة:

حارس النوع instanceof:

// فئات للعرض التوضيحي
class Dog {
  bark(): void {
    console.log('Woof!');
  }
}

class Cat {
  meow(): void {
    console.log('Meow!');
  }
}

// دالة تستخدم instanceof
function makeSound(animal: Dog | Cat): void {
  if (animal instanceof Dog) {
    // TypeScript تعرف أن animal هي Dog هنا
    animal.bark();
  } else {
    // TypeScript تعرف أن animal هي Cat هنا
    animal.meow();
  }
}

const myDog = new Dog();
const myCat = new Cat();

makeSound(myDog); // "Woof!"
makeSound(myCat); // "Meow!"

// instanceof مع الأنواع المدمجة
function processInput(input: Date | string): string {
  if (input instanceof Date) {
    // TypeScript تعرف أن input هي Date هنا
    return input.toISOString();
  } else {
    // TypeScript تعرف أن input هي string هنا
    return input.toUpperCase();
  }
}

console.log(processInput(new Date()));
console.log(processInput('hello'));

// instanceof مع معالجة الأخطاء
function handleError(error: Error | string): void {
  if (error instanceof Error) {
    console.error(`Error: ${error.message}`);
    console.error(`Stack: ${error.stack}`);
  } else {
    console.error(`Error: ${error}`);
  }
}

handleError(new Error('Something went wrong'));
handleError('Simple error message');

حراس الأنواع - عامل in

عامل in يتحقق مما إذا كانت الخاصية موجودة في كائن:

حارس النوع عامل in:

// أنواع مع خصائص مختلفة
type Fish = {
  swim: () => void;
  fins: number;
};

type Bird = {
  fly: () => void;
  wings: number;
};

// دالة تستخدم عامل 'in'
function move(animal: Fish | Bird): void {
  if ('swim' in animal) {
    // TypeScript تعرف أن animal هي Fish هنا
    console.log(`Swimming with ${animal.fins} fins`);
    animal.swim();
  } else {
    // TypeScript تعرف أن animal هي Bird هنا
    console.log(`Flying with ${animal.wings} wings`);
    animal.fly();
  }
}

const fish: Fish = {
  swim: () => console.log('Swimming...'),
  fins: 4
};

const bird: Bird = {
  fly: () => console.log('Flying...'),
  wings: 2
};

move(fish); // "Swimming with 4 fins", "Swimming..."
move(bird); // "Flying with 2 wings", "Flying..."

// 'in' مع الخصائص الاختيارية
type UserProfile = {
  name: string;
  email: string;
  phone?: string;
  address?: {
    city: string;
    country: string;
  };
};

function displayContact(profile: UserProfile): void {
  console.log(`Name: ${profile.name}`);
  console.log(`Email: ${profile.email}`);

  if ('phone' in profile && profile.phone) {
    console.log(`Phone: ${profile.phone}`);
  }

  if ('address' in profile && profile.address) {
    console.log(`City: ${profile.address.city}`);
  }
}
نصيحة: عامل in مثالي للتمييز بين أنواع الكائنات بناءً على خصائصها. إنه مفيد بشكل خاص عند العمل مع كائنات من واجهات برمجة التطبيقات الخارجية.

حراس الأنواع المخصصة (محددات الأنواع)

حراس الأنواع المخصصة تستخدم محددات الأنواع (الكلمة المفتاحية is) لإنشاء دوال فحص أنواع قابلة لإعادة الاستخدام:

دوال محدد النوع:

// دالة حارس النوع المخصصة
function isString(value: unknown): value is string {
  return typeof value === 'string';
}

// الاستخدام
function processValue(value: unknown): void {
  if (isString(value)) {
    // TypeScript تعرف أن value هي string هنا
    console.log(value.toUpperCase());
  } else {
    console.log('Not a string');
  }
}

// حارس نوع أكثر تعقيداً
interface User {
  id: string;
  name: string;
  email: string;
}

interface Admin {
  id: string;
  name: string;
  email: string;
  permissions: string[];
}

// حارس النوع للتحقق مما إذا كان User هو Admin
function isAdmin(user: User | Admin): user is Admin {
  return 'permissions' in user;
}

function grantAccess(user: User | Admin): void {
  if (isAdmin(user)) {
    // TypeScript تعرف أن user هي Admin هنا
    console.log(`Admin with ${user.permissions.length} permissions`);
  } else {
    // TypeScript تعرف أن user هي User هنا
    console.log(`Regular user: ${user.name}`);
  }
}

// حارس نوع المصفوفة
function isStringArray(value: unknown): value is string[] {
  return (
    Array.isArray(value) &&
    value.every(item => typeof item === 'string')
  );
}

function processArray(value: unknown): void {
  if (isStringArray(value)) {
    // TypeScript تعرف أن value هي string[]
    value.forEach(str => console.log(str.toUpperCase()));
  } else {
    console.log('Not a string array');
  }
}

// حارس النوع القابل للإلغاء
function isDefined<T>(value: T | null | undefined): value is T {
  return value !== null && value !== undefined;
}

// تصفية المصفوفة إزالة null/undefined
const values: (string | null | undefined)[] = ['a', null, 'b', undefined, 'c'];
const definedValues = values.filter(isDefined);
// النوع هو string[] (تمت إزالة null و undefined)
console.log(definedValues); // ['a', 'b', 'c']
ملاحظة: محددات الأنواع تستخدم بناء الجملة parameterName is Type في نوع الإرجاع. يجب أن ترجع الدالة قيمة منطقية، وستقوم TypeScript بتضييق النوع وفقاً لذلك.

الاتحادات المميزة مع حراس الأنواع

حراس الأنواع تعمل بشكل استثنائي مع الاتحادات المميزة:

الاتحادات المميزة:

// اتحاد مميز
type SuccessResponse = {
  status: 'success';
  data: {
    id: string;
    name: string;
  };
};

type ErrorResponse = {
  status: 'error';
  error: {
    code: string;
    message: string;
  };
};

type ApiResponse = SuccessResponse | ErrorResponse;

// حارس النوع باستخدام المميز
function isSuccessResponse(
  response: ApiResponse
): response is SuccessResponse {
  return response.status === 'success';
}

function isErrorResponse(
  response: ApiResponse
): response is ErrorResponse {
  return response.status === 'error';
}

// استخدام حراس الأنواع
function handleResponse(response: ApiResponse): void {
  if (isSuccessResponse(response)) {
    // TypeScript تعرف أن response هي SuccessResponse
    console.log(`Success: ${response.data.name}`);
  } else if (isErrorResponse(response)) {
    // TypeScript تعرف أن response هي ErrorResponse
    console.error(`Error ${response.error.code}: ${response.error.message}`);
  }
}

// بديل: استخدام المميز مباشرة في switch
function handleResponseSwitch(response: ApiResponse): void {
  switch (response.status) {
    case 'success':
      console.log(`Success: ${response.data.name}`);
      break;
    case 'error':
      console.error(`Error: ${response.error.message}`);
      break;
  }
}

الجمع بين حراس الأنواع

يمكنك الجمع بين حراس أنواع متعددة لتضييق النوع المعقد:

الجمع بين حراس الأنواع:

// بنية نوع معقدة
type User = {
  type: 'user';
  name: string;
  email: string;
};

type Admin = {
  type: 'admin';
  name: string;
  email: string;
  permissions: string[];
};

type Guest = {
  type: 'guest';
  sessionId: string;
};

type Account = User | Admin | Guest;

// حراس أنواع متعددة
function hasEmail(account: Account): account is User | Admin {
  return account.type === 'user' || account.type === 'admin';
}

function isAdmin(account: Account): account is Admin {
  return account.type === 'admin';
}

// استخدام حراس الأنواع المدمجة
function processAccount(account: Account): void {
  if (hasEmail(account)) {
    // account هي User | Admin (كلاهما لديه email)
    console.log(`Email: ${account.email}`);

    if (isAdmin(account)) {
      // account هي Admin
      console.log(`Permissions: ${account.permissions.join(', ')}`);
    }
  } else {
    // account هي Guest
    console.log(`Guest session: ${account.sessionId}`);
  }
}

// تضييق النوع المتداخل
function getDisplayName(account: Account): string {
  if ('name' in account) {
    // account هي User | Admin
    return account.name;
  } else {
    // account هي Guest
    return `Guest (${account.sessionId})`;
  }
}

حراس الأنواع مع المصفوفات

حراس الأنواع مفيدة لتصفية ومعالجة المصفوفات:

حراس أنواع المصفوفة:

// نوع مصفوفة مختلط
type Item = string | number | boolean | null;

const items: Item[] = ['hello', 42, true, null, 'world', 0, false];

// تصفية السلاسل
const strings = items.filter((item): item is string => {
  return typeof item === 'string';
});
console.log(strings); // ['hello', 'world']

// تصفية الأرقام (استثناء الصفر إذا لزم الأمر)
const numbers = items.filter((item): item is number => {
  return typeof item === 'number' && item !== 0;
});
console.log(numbers); // [42]

// تصفية القيم غير الفارغة
const nonNullItems = items.filter((item): item is string | number | boolean => {
  return item !== null;
});
console.log(nonNullItems); // الكل باستثناء null

// مصفوفة كائنات معقدة
type Product = {
  id: string;
  name: string;
  price: number;
  discount?: number;
};

const products: Product[] = [
  { id: '1', name: 'Laptop', price: 999, discount: 0.1 },
  { id: '2', name: 'Mouse', price: 29 },
  { id: '3', name: 'Keyboard', price: 79, discount: 0.15 }
];

// تصفية المنتجات مع الخصومات
function hasDiscount(product: Product): product is Product & { discount: number } {
  return product.discount !== undefined && product.discount > 0;
}

const discountedProducts = products.filter(hasDiscount);
discountedProducts.forEach(product => {
  // TypeScript تعرف أن discount محدد
  console.log(`${product.name}: ${product.discount * 100}% off`);
});
تمرين:
  1. أنشئ اتحاداً مميزاً Shape بثلاثة أنواع: Circle (kind: 'circle', radius: number)، Rectangle (kind: 'rectangle', width: number, height: number)، وSquare (kind: 'square', size: number)
  2. أنشئ دوال حارس النوع المخصصة isCircle، isRectangle، وisSquare لكل نوع شكل
  3. أنشئ دالة calculateArea التي تستخدم حراس الأنواع لحساب مساحة أي شكل
  4. أنشئ دالة isLargeShape التي ترجع true إذا كانت مساحة الشكل أكبر من 100
  5. اختبر دوالك مع مصفوفة من الأشكال المختلطة وقم بتصفية الكبيرة منها

الملخص

  • تأكيدات الأنواع تستخدم الكلمة المفتاحية as لإخبار TypeScript عن نوع القيمة
  • تأكيد غير الفارغ (!) يؤكد أن القيمة ليست null أو undefined
  • حارس النوع typeof يتحقق من الأنواع البدائية في وقت التشغيل
  • حارس النوع instanceof يتحقق مما إذا كان الكائن هو نسخة من فئة
  • حارس النوع عامل in يتحقق مما إذا كانت الخاصية موجودة في كائن
  • حراس الأنواع المخصصة تستخدم محددات الأنواع (is) لفحوصات الأنواع القابلة لإعادة الاستخدام
  • حراس الأنواع تمكن التضييق الآمن للنوع بناءً على فحوصات وقت التشغيل
  • اجمع حراس الأنواع لسيناريوهات التضييق المعقدة للنوع
  • تأكيدات الأنواع تتجاوز الفحوصات؛ حراس الأنواع توفر تضييقاً آمناً