تأكيدات الأنواع وحراس الأنواع
تأكيدات الأنواع وحراس الأنواع
توفر TypeScript آليات قوية للعمل مع الأنواع في وقت التشغيل. تأكيدات الأنواع تسمح لك بإخبار المترجم "ثق بي، أعرف ما أفعله"، بينما حراس الأنواع تتيح لك تضييق الأنواع بأمان بناءً على فحوصات وقت التشغيل. فهم هذه المفاهيم ضروري لكتابة كود TypeScript مرن وآمن من حيث الأنواع.
تأكيدات الأنواع
تأكيدات الأنواع هي طريقة لإخبار TypeScript بأنك تعرف أكثر عن نوع القيمة مما يعرفه. إنها لا تقوم بأي فحوصات أو تحويلات في وقت التشغيل؛ إنها فقط للمترجم.
// تأكيد النوع الأساسي
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 أيضاً بناء جملة القوس الزاوي لتأكيدات الأنواع، على الرغم من أن 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 مع الأنواع البدائية
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 يتحقق مما إذا كان الكائن هو نسخة من فئة معينة:
// فئات للعرض التوضيحي
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 يتحقق مما إذا كانت الخاصية موجودة في كائن:
// أنواع مع خصائص مختلفة
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`);
});
- أنشئ اتحاداً مميزاً
Shapeبثلاثة أنواع:Circle(kind: 'circle', radius: number)،Rectangle(kind: 'rectangle', width: number, height: number)، وSquare(kind: 'square', size: number) - أنشئ دوال حارس النوع المخصصة
isCircle،isRectangle، وisSquareلكل نوع شكل - أنشئ دالة
calculateAreaالتي تستخدم حراس الأنواع لحساب مساحة أي شكل - أنشئ دالة
isLargeShapeالتي ترجع true إذا كانت مساحة الشكل أكبر من 100 - اختبر دوالك مع مصفوفة من الأشكال المختلطة وقم بتصفية الكبيرة منها
الملخص
- تأكيدات الأنواع تستخدم الكلمة المفتاحية
asلإخبار TypeScript عن نوع القيمة - تأكيد غير الفارغ (
!) يؤكد أن القيمة ليست null أو undefined - حارس النوع typeof يتحقق من الأنواع البدائية في وقت التشغيل
- حارس النوع instanceof يتحقق مما إذا كان الكائن هو نسخة من فئة
- حارس النوع عامل in يتحقق مما إذا كانت الخاصية موجودة في كائن
- حراس الأنواع المخصصة تستخدم محددات الأنواع (
is) لفحوصات الأنواع القابلة لإعادة الاستخدام - حراس الأنواع تمكن التضييق الآمن للنوع بناءً على فحوصات وقت التشغيل
- اجمع حراس الأنواع لسيناريوهات التضييق المعقدة للنوع
- تأكيدات الأنواع تتجاوز الفحوصات؛ حراس الأنواع توفر تضييقاً آمناً