أنواع الكائنات
أنواع الكائنات
أنواع الكائنات أساسية في TypeScript، حيث تسمح لك بوصف شكل وبنية الكائنات في الكود الخاص بك. فهم كيفية كتابة أنواع الكائنات بشكل صحيح أمر ضروري لبناء تطبيقات آمنة من حيث الأنواع. توفر TypeScript طرقاً متنوعة لتعريف والعمل مع أنواع الكائنات، من تعليقات الخصائص البسيطة إلى الميزات المتقدمة مثل توقيعات الفهرس والخصائص للقراءة فقط.
تعليق نوع الكائن
أبسط طريقة لكتابة نوع الكائن هي باستخدام تعليق نوع الكائن المضمن:
// نوع الكائن المضمن
let user: {
name: string;
age: number;
email: string;
} = {
name: 'Alice',
age: 30,
email: 'alice@example.com'
};
// معامل الدالة مع نوع الكائن
function displayUser(user: {
name: string;
age: number;
isActive: boolean;
}): void {
console.log(`User: ${user.name}, Age: ${user.age}`);
console.log(`Status: ${user.isActive ? 'Active' : 'Inactive'}`);
}
displayUser({
name: 'Bob',
age: 25,
isActive: true
});
// دالة ترجع نوع كائن
function createProduct(
id: string,
name: string,
price: number
): {
id: string;
name: string;
price: number;
createdAt: Date;
} {
return {
id,
name,
price,
createdAt: new Date()
};
}
const product = createProduct('prod-001', 'Laptop', 999);
console.log(product.createdAt); // TypeScript تعرف أن هذا Date
الخصائص الاختيارية
الخصائص الاختيارية تسمح لخصائص معينة بأن تكون موجودة أو غائبة في كائن. حدد الخصائص كاختيارية باستخدام علامة الاستفهام (?):
// نوع مع خصائص اختيارية
type UserProfile = {
username: string;
email: string;
bio?: string; // اختيارية
website?: string; // اختيارية
phone?: string; // اختيارية
};
// صحيح: يتضمن جميع الخصائص المطلوبة
const user1: UserProfile = {
username: 'john_doe',
email: 'john@example.com'
};
// صحيح: يتضمن خصائص اختيارية
const user2: UserProfile = {
username: 'jane_smith',
email: 'jane@example.com',
bio: 'Software developer',
website: 'https://janesmith.dev'
};
// دالة تتعامل مع الخصائص الاختيارية
function displayProfile(profile: UserProfile): void {
console.log(`Username: ${profile.username}`);
console.log(`Email: ${profile.email}`);
// تحقق من وجود الخاصية الاختيارية
if (profile.bio) {
console.log(`Bio: ${profile.bio}`);
}
// التسلسل الاختياري مع الخصائص الاختيارية
console.log(`Website: ${profile.website ?? 'Not provided'}`);
}
// خصائص اختيارية متداخلة
type Address = {
street: string;
city: string;
state: string;
zipCode: string;
country?: string;
};
type Customer = {
name: string;
email: string;
address?: Address; // العنوان بأكمله اختياري
};
const customer1: Customer = {
name: 'Alice',
email: 'alice@example.com'
};
const customer2: Customer = {
name: 'Bob',
email: 'bob@example.com',
address: {
street: '123 Main St',
city: 'New York',
state: 'NY',
zipCode: '10001',
country: 'USA'
}
};
?.) وعامل الدمج الفارغ (??) للوصول بأمان وتوفير قيم افتراضية للخصائص الاختيارية.
الخصائص للقراءة فقط
الخصائص للقراءة فقط يمكن تعيينها فقط أثناء التهيئة ولا يمكن تعديلها بعد ذلك. استخدم المعدل readonly:
// نوع مع خصائص للقراءة فقط
type Config = {
readonly apiKey: string;
readonly baseUrl: string;
readonly timeout: number;
retryAttempts: number; // ليست للقراءة فقط، يمكن تعديلها
};
const config: Config = {
apiKey: 'secret-key-123',
baseUrl: 'https://api.example.com',
timeout: 5000,
retryAttempts: 3
};
// صحيح: تعديل خاصية ليست للقراءة فقط
config.retryAttempts = 5;
// خطأ: لا يمكن التعيين إلى خاصية للقراءة فقط
// config.apiKey = 'new-key';
// config.baseUrl = 'https://new-api.com';
// للقراءة فقط مع كائنات متداخلة
type Article = {
readonly id: string;
title: string;
content: string;
readonly metadata: {
readonly createdAt: Date;
readonly author: string;
views: number; // ليست للقراءة فقط
};
};
const article: Article = {
id: 'article-001',
title: 'TypeScript Guide',
content: 'Learn TypeScript...',
metadata: {
createdAt: new Date(),
author: 'John Doe',
views: 0
}
};
// صحيح: تعديل خاصية متداخلة ليست للقراءة فقط
article.metadata.views = 100;
// صحيح: تعديل خاصية المستوى الأعلى ليست للقراءة فقط
article.title = 'Complete TypeScript Guide';
// خطأ: لا يمكن تعديل الخصائص للقراءة فقط
// article.id = 'new-id';
// article.metadata.createdAt = new Date();
// article.metadata.author = 'Jane Doe';
// مصفوفات للقراءة فقط
type TodoList = {
readonly items: readonly string[];
};
const todos: TodoList = {
items: ['Task 1', 'Task 2', 'Task 3']
};
// خطأ: لا يمكن تعديل المصفوفة للقراءة فقط
// todos.items.push('Task 4');
// todos.items[0] = 'Updated Task';
readonly هو فحص وقت الترجمة فقط. لا يجعل الكائنات غير قابلة للتغيير في وقت التشغيل. إذا كنت بحاجة إلى عدم القابلية للتغيير الحقيقي، فكر في استخدام Object.freeze() أو هياكل البيانات غير القابلة للتغيير.
توقيعات الفهرس
توقيعات الفهرس تسمح لك بوصف الكائنات التي يمكن أن تحتوي على خصائص بمفاتيح ديناميكية. هذا مفيد عندما لا تعرف جميع أسماء الخصائص مسبقاً:
// توقيع فهرس السلسلة
type StringDictionary = {
[key: string]: string;
};
const translations: StringDictionary = {
hello: 'Hola',
goodbye: 'Adiós',
thanks: 'Gracias'
};
// إضافة خصائص جديدة بشكل ديناميكي
translations.welcome = 'Bienvenido';
console.log(translations.hello); // "Hola"
// توقيع فهرس الرقم
type NumberArray = {
[index: number]: string;
};
const fruits: NumberArray = {
0: 'Apple',
1: 'Banana',
2: 'Orange'
};
console.log(fruits[1]); // "Banana"
// توقيع الفهرس مع خصائص معروفة
type UserData = {
name: string; // خاصية معروفة
email: string; // خاصية معروفة
[key: string]: string; // خصائص ديناميكية إضافية
};
const userData: UserData = {
name: 'Alice',
email: 'alice@example.com',
phone: '+1234567890', // خاصية ديناميكية
address: '123 Main St' // خاصية ديناميكية
};
// أنواع قيم مختلطة مع توقيع الفهرس
type MixedData = {
[key: string]: string | number | boolean;
};
const settings: MixedData = {
theme: 'dark',
fontSize: 16,
autoSave: true,
language: 'en'
};
// توقيع فهرس للقراءة فقط
type ReadonlyDictionary = {
readonly [key: string]: number;
};
const scores: ReadonlyDictionary = {
math: 95,
science: 88,
history: 92
};
// خطأ: لا يمكن تعديل الخصائص في توقيع الفهرس للقراءة فقط
// scores.math = 100;
[key: string]: number، لا يمكنك إضافة خاصية بقيمة سلسلة.
فحوصات الخصائص الزائدة
تقوم TypeScript بإجراء فحوصات الخصائص الزائدة عند تعيين حروف الكائن إلى متغيرات مكتوبة. هذا يمنع الأخطاء المطبعية ويضمن أن الكائنات لديها فقط الخصائص المتوقعة:
type User = {
name: string;
age: number;
};
// صحيح: الكائن لديه بالضبط الخصائص المتوقعة
const user1: User = {
name: 'Alice',
age: 30
};
// خطأ: الخاصية الزائدة 'email' غير موجودة في النوع 'User'
// const user2: User = {
// name: 'Bob',
// age: 25,
// email: 'bob@example.com' // خاصية زائدة
// };
// حل بديل 1: تأكيد النوع (استخدمه بحذر)
const user3: User = {
name: 'Charlie',
age: 35,
email: 'charlie@example.com'
} as User;
// حل بديل 2: التعيين إلى متغير أولاً
const tempUser = {
name: 'David',
age: 28,
email: 'david@example.com'
};
const user4: User = tempUser; // لا خطأ، تم تجاوز فحص الخاصية الزائدة
// حل بديل 3: استخدم توقيع الفهرس
type FlexibleUser = {
name: string;
age: number;
[key: string]: unknown; // السماح بخصائص إضافية
};
const user5: FlexibleUser = {
name: 'Eve',
age: 32,
email: 'eve@example.com', // الآن مسموح
phone: '+1234567890' // الآن مسموح
};
// معاملات الدالة تؤدي أيضاً إلى فحوصات الخصائص الزائدة
function createUser(user: User): void {
console.log(`Created user: ${user.name}`);
}
// خطأ: خاصية زائدة 'email'
// createUser({
// name: 'Frank',
// age: 40,
// email: 'frank@example.com'
// });
// صحيح: خصائص دقيقة
createUser({
name: 'Grace',
age: 27
});
أنواع الكائنات المتداخلة
يمكن أن تحتوي الكائنات على كائنات أخرى، مما ينشئ هياكل متداخلة:
// نوع كائن متداخل بعمق
type Company = {
name: string;
address: {
street: string;
city: string;
country: string;
coordinates: {
latitude: number;
longitude: number;
};
};
employees: {
count: number;
departments: {
name: string;
headCount: number;
}[];
};
contact: {
email: string;
phone: string;
social: {
twitter?: string;
linkedin?: string;
github?: string;
};
};
};
const company: Company = {
name: 'Tech Corp',
address: {
street: '100 Tech Avenue',
city: 'San Francisco',
country: 'USA',
coordinates: {
latitude: 37.7749,
longitude: -122.4194
}
},
employees: {
count: 500,
departments: [
{ name: 'Engineering', headCount: 200 },
{ name: 'Sales', headCount: 150 },
{ name: 'Marketing', headCount: 100 }
]
},
contact: {
email: 'info@techcorp.com',
phone: '+1-555-0123',
social: {
twitter: '@techcorp',
linkedin: 'techcorp'
}
}
};
// الوصول إلى الخصائص المتداخلة
console.log(company.address.city); // "San Francisco"
console.log(company.address.coordinates.latitude); // 37.7749
console.log(company.employees.departments[0].name); // "Engineering"
console.log(company.contact.social.twitter); // "@techcorp"
// التسلسل الاختياري مع الكائنات المتداخلة
console.log(company.contact.social.github ?? 'Not provided');
أدوات أنواع الكائنات المساعدة
توفر TypeScript أنواع مساعدة مدمجة للعمل مع أنواع الكائنات:
// النوع الأصلي
type User = {
id: string;
name: string;
email: string;
age: number;
isActive: boolean;
};
// Partial<T> - يجعل جميع الخصائص اختيارية
type PartialUser = Partial<User>;
const updateUser: PartialUser = {
name: 'Alice',
age: 31
// الخصائص الأخرى اختيارية
};
// Required<T> - يجعل جميع الخصائص مطلوبة
type OptionalUser = {
name?: string;
email?: string;
age?: number;
};
type RequiredUser = Required<OptionalUser>;
const fullUser: RequiredUser = {
name: 'Bob',
email: 'bob@example.com',
age: 25 // جميع الخصائص الآن مطلوبة
};
// Readonly<T> - يجعل جميع الخصائص للقراءة فقط
type ReadonlyUser = Readonly<User>;
const user: ReadonlyUser = {
id: '123',
name: 'Charlie',
email: 'charlie@example.com',
age: 30,
isActive: true
};
// خطأ: لا يمكن تعديل الخصائص للقراءة فقط
// user.name = 'David';
// Pick<T, K> - ينشئ نوعاً مع مجموعة فرعية من الخصائص
type UserPreview = Pick<User, 'id' | 'name' | 'email'>;
const preview: UserPreview = {
id: '456',
name: 'Eve',
email: 'eve@example.com'
// age و isActive غير مطلوبة
};
// Omit<T, K> - ينشئ نوعاً يستثني الخصائص المحددة
type UserWithoutId = Omit<User, 'id' | 'isActive'>;
const newUser: UserWithoutId = {
name: 'Frank',
email: 'frank@example.com',
age: 35
// id و isActive مستثناة
};
// Record<K, T> - ينشئ نوع كائن بمفاتيح محددة ونوع قيمة
type UserRoles = Record<string, boolean>;
const roles: UserRoles = {
admin: true,
editor: false,
viewer: true
};
// الجمع بين الأدوات المساعدة
type PartialReadonlyUser = Partial<Readonly<User>>;
type RequiredUserPreview = Required<Pick<User, 'name' | 'email'>>;
تفكيك الكائن مع الأنواع
تعمل TypeScript بسلاسة مع تفكيك الكائن:
type Product = {
id: string;
name: string;
price: number;
category: string;
inStock: boolean;
};
// التفكيك في معاملات الدالة
function displayProduct({ name, price, inStock }: Product): void {
console.log(`Product: ${name}`);
console.log(`Price: $${price}`);
console.log(`In Stock: ${inStock ? 'Yes' : 'No'}`);
}
const laptop: Product = {
id: 'prod-001',
name: 'MacBook Pro',
price: 2499,
category: 'Electronics',
inStock: true
};
displayProduct(laptop);
// التفكيك مع إعادة التسمية
function processUser({ name: userName, email: userEmail }: {
name: string;
email: string;
}): void {
console.log(`User: ${userName}`);
console.log(`Email: ${userEmail}`);
}
// التفكيك المتداخل
type Order = {
id: string;
customer: {
name: string;
email: string;
};
items: {
product: string;
quantity: number;
}[];
};
function processOrder({
id,
customer: { name, email },
items
}: Order): void {
console.log(`Order ID: ${id}`);
console.log(`Customer: ${name} (${email})`);
console.log(`Items: ${items.length}`);
}
- أنشئ نوعاً
Bookمع الخصائص:readonly id: string،title: string،author: string،publishedYear: number، اختياريةisbn?: string، واختياريةtags?: string[] - أنشئ نوعاً
Libraryمع توقيع فهرس سلسلة يربط معرفات الكتب بكائناتBook، بالإضافة إلى خاصيةname: string - أنشئ دالة
addBookالتي تأخذLibraryوBook، تضيف الكتاب إلى المكتبة، وترجع المكتبة المحدثة - أنشئ نوعاً
BookSummaryباستخدامPickلاستخراج فقطtitle،author، وpublishedYearمنBook - أنشئ نسخاً من
Book،Library، واختبر دالةaddBookالخاصة بك
الملخص
- تعليق نوع الكائن يصف شكل وبنية الكائنات
- الخصائص الاختيارية تستخدم
?لجعل الخصائص اختيارية - الخصائص للقراءة فقط تستخدم
readonlyلمنع التعديل بعد التهيئة - توقيعات الفهرس تسمح بالكائنات مع أسماء خصائص ديناميكية
- فحوصات الخصائص الزائدة تمنع الأخطاء المطبعية عن طريق رفض الخصائص غير المتوقعة في حروف الكائن
- الكائنات المتداخلة تنشئ هياكل بيانات هرمية معقدة
- الأنواع المساعدة مثل
Partial،Required،Readonly،Pick، وOmitتحول أنواع الكائنات - التفكيك يعمل بسلاسة مع أنواع TypeScript