TypeScript: أنواع الكائنات في TypeScript

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

في لغة جافا سكريبت، تُعد الكائنات أكثر هياكل البيانات استخدامًا. ويتيح لك نوع الكائن في لغة تايب سكريبت وصف «الخصائص التي يجب أن يتضمنها الكائن وأنواعها» بدقة — وهذا يمثل جوهر نظام الأنواع المنظم الخاص بها.

1. أساسيات أنواع الكائنات

(1) أنواع الكائنات المجهولة

الطريقة الأبسط — تحديد بنية الكائن مباشرةً عند إعلان النوع:

TYPESCRIPT
let user: { name: string; age: number } = {
  name: "Charlie",
  age: 20
};

(2) استدلال الأنواع

عندما يتم تعيين قيمة حرفية لكائن إلى متغير، يقوم TypeScript باستنتاج النوع تلقائيًا:

TYPESCRIPT
let user = {
  name: "Charlie",
  age: 20
};
// Inferred as { name: string; age: number }

user.name = "Diana";   // ✅
user.age = 21;        // ✅
// user.email = "x@x.com";  // ❌ Not included in the types of inferences email Properties

(3) بناء الجملة متعدد الأسطر

عندما يحتوي كائن ما على العديد من الخصائص، يُنصح باستخدام تنسيق متعدد الأسطر (مفصولة بفواصل منقوطة أو فواصل):

TYPESCRIPT
let product: {
  id: number;
  name: string;
  price: number;
  inStock: boolean;
} = {
  id: 1,
  name: "TypeScriptAuthoritative Guide",
  price: 89.9,
  inStock: true
};

▶ مثال: أنواع الكائنات لمعلمات الدالة

TYPESCRIPT
function createUser(user: { name: string; age: number; email: string }): string {
  return `${user.name},${user.age} years old,Email:${user.email}`;
}

console.log(createUser({
  name: "Charlie",
  age: 20,
  email: "xiaoming@example.com"
}));
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
Charlie,20 years old,Email:xiaoming@example.com


2. الخصائص الاختيارية

استخدم ? لتمييز سمة اختيارية — يمكن حذف هذه السمة:

(1) قواعد النحو

TYPESCRIPT
let user: { name: string; age?: number } = {
  name: "Charlie"
  // age This information is optional.
};

console.log(user.name);  // "Charlie"
console.log(user.age);   // undefined

(2) أنواع الصفات الاختيارية

تشمل أنواع السمات الاختيارية تلقائيًا undefined:

TYPESCRIPT
// age?: number Equivalent to age: number | undefined
// But there is an important difference:age?: number The property does not exist.,age: number | undefined The property must exist.

let config: { host: string; port?: number } = { host: "localhost" };
// config.port is undefined(The property does not exist either. ok 's)

let config2: { host: string; port: number | undefined } = { host: "localhost" };
// ❌ Error! port property must exist,Even if undefined You must also explicitly write
// Correct Way to Write It:{ host: "localhost", port: undefined }

(3) طرق آمنة للوصول إلى السمات الاختيارية

TYPESCRIPT
interface User {
  name: string;
  age?: number;
}

function getAgeText(user: User): string {
  // ❌ Unsafe——If age Does not exist,undefined.toFixed() It crashes during runtime
  // return user.age.toFixed(0) + " years old";

  // ✅ Method 1:Check first
  if (user.age !== undefined) {
    return user.age.toFixed(0) + " years old";
  }
  return "Age unknown";

  // ✅ Method 2:Provide a default value
  // return (user.age ?? 0).toFixed(0) + " years old";
}


3. الخصائص المخصصة للقراءة فقط

استخدم العلامة readonly لتعيين خاصية ما على أنها للقراءة فقط — ولا يمكن تعديلها بعد التعيين:

TYPESCRIPT
let user: { readonly id: number; name: string } = {
  id: 1,
  name: "Charlie"
};

user.name = "Diana";     // ✅ General attributes can be modified
// user.id = 2;         // ❌ Read-only properties cannot be modified.

(2) readonly هو مُعدِّل سطحي للقراءة فقط

TYPESCRIPT
let config: { readonly data: { value: number } } = {
  data: { value: 42 }
};

// config.data = { value: 100 };  // ❌ data Cannot be reassigned
config.data.value = 100;          // ✅ data The internal properties can still be modified!
💡 القراءة فقط الشاملة: إذا كنت تريد أن تكون جميع مستويات كائن ما للقراءة فقط، فاستخدم نوع الأداة Readonly<type> (متداخلة) أو التأكيد as const. سيتناول الدرس 19 أنواع الأدوات Readonly وPartial وRequired وغيرها.



4. توقيعات الفهرس

عندما يكون اسم خاصية كائن ما غير معروف، لكن نوع قيمة تلك الخاصية معروف، استخدم توقيع الفهرس:

(1) توقيع مؤشر السلسلة

TYPESCRIPT
let scores: { [key: string]: number } = {
  Chinese Language: 90,
  Mathematics: 95,
  English: 88
};

console.log(scores["Mathematics"]);   // 95

// Add a New Property——As long as it is string key + number Any value will do.
scores["Physics"] = 85;   // ✅

(2) التوقيعات الرقمية المفهرسة

TYPESCRIPT
let arr: { [index: number]: string } = {
  0: "first",
  1: "second",
  2: "third"
};

console.log(arr[1]);  // "second"

(3) القيود المفروضة على التوقيعات المؤشرية

يتطلب توقيع الفهرس أن تكون أنواع جميع السمات المعروفة أنواعًا فرعية لنوع قيمة الفهرس:

TYPESCRIPT
// ❌ Error: name is string,However, index signatures require that all values be number
// let config: { [key: string]: number; name: string } = { name: "test" };

// ✅ Correct:All value types are consistent
let config: { [key: string]: string; name: string } = { name: "test" };

(4) توقيعات الفهرس في القيم الثابتة للنماذج (TypeScript 4.4+)

TYPESCRIPT
let css: { [key: `--${string}`]: string } = {
  "--primary-color": "#4A90D9",
  "--font-size": "14px"
};

▶ مثال: تنفيذ التكوين الديناميكي باستخدام توقيعات الفهرس

TYPESCRIPT
interface EnvConfig {
  [key: string]: string | number | boolean;
  // All values can be string、number or boolean
  // Known properties must also be compatible.
  NODE_ENV: string;
  PORT: number;
}

let env: EnvConfig = {
  NODE_ENV: "development",
  PORT: 3000,
  DEBUG: true,        // ✅ Dynamic Properties
  API_URL: "http://localhost:8080"  // ✅ Dynamic Properties
};

console.log(`Environment:${env.NODE_ENV}`);
console.log(`Port:${env.PORT}`);
console.log(`Debugging:${env.DEBUG}`);
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
Environment:development
Port:3000
Debugging:true


5. أنواع الكائنات المتداخلة

في عملية التطوير الفعلية، غالبًا ما تكون الكائنات متداخلة على مستويات متعددة — ويدعم TypeScript وصف الأنواع طبقةً تلو الأخرى:

(1) أنواع القيم الثابتة المتداخلة

TYPESCRIPT
let user: {
  name: string;
  address: {
    city: string;
    zip: string;
  };
  tags: string[];
} = {
  name: "Charlie",
  address: {
    city: "Beijing",
    zip: "100000"
  },
  tags: ["Developer", "TypeScript"]
};

console.log(user.address.city);  // "Beijing"
console.log(user.tags[0]);       // "Developer"

(2) التداخل متعدد المستويات

TYPESCRIPT
type ApiResponse = {
  status: number;
  data: {
    user: {
      id: number;
      profile: {
        avatar: string;
        bio: string;
      };
    };
  };
};

let response: ApiResponse = {
  status: 200,
  data: {
    user: {
      id: 1,
      profile: {
        avatar: "https://example.com/avatar.png",
        bio: "TypeScript Enthusiast"
      }
    }
  }
};

console.log(response.data.user.profile.bio);  // "TypeScript Enthusiast"
💡 نصيحة: عندما يتجاوز التداخل 2–3 مستويات، ينبغي عليك استخراج الأنواع الداخلية إلى واجهات منفصلة أو أسماء مستعارة للأنواع لتحسين قابلية القراءة وإمكانية إعادة الاستخدام. سيتم تناول هذا الموضوع بالتفصيل في الدروس 9–11.



6. نمطان من أنواع الكائنات

(1) الأنواع المجهولة (المضمنة)

TYPESCRIPT
function greet(user: { name: string; age: number }): string {
  return `Hello,${user.name}!`;
}

مناسب لـ: الأنواع البسيطة التي تُستخدم مرة واحدة فقط، ومعلمات الدوال

(2) أنواع التسمية (واجهة / نوع)

TYPESCRIPT
interface User {
  name: string;
  age: number;
}

function greet(user: User): string {
  return `Hello,${user.name}!`;
}

مناسب لـ: الاستخدامات المتعددة، والخصائص العديدة، والحالات التي تتطلب التوريث أو التوسيع

📌 توصية: إذا كان هناك أكثر من ثلاث خصائص أو إذا كنت بحاجة إلى إعادة استخدام نوع ما، فقم باستخراجه إلى واجهة أو اسم مستعار للنوع. أما بالنسبة للمعلمات البسيطة التي تُستخدم مرة واحدة، فاستخدم نوعًا مجهولًا.



7. أنظمة الأنواع المنظمة

تستخدم TypeScript نظام أنواع منظم — فما دام هيكل الكائن يستوفي متطلبات النوع، فإنه يعتبر متوافقًا، بغض النظر عما إذا كانت أسماء الأنواع متطابقة أم لا:

(1) المطابقة الهيكلية

TYPESCRIPT
interface Point {
  x: number;
  y: number;
}

let point: Point = { x: 1, y: 2 };   // ✅ Has x and y

let point3D = { x: 1, y: 2, z: 3 };
point = point3D;   // ✅ point3D has x and y,Redundant attributes do not conflict

(2) التحقق من وجود السمات غير الضرورية

عند تعيين قيمة مباشرةً إلى كائن حرفي، يقوم TypeScript بإجراء «فحص للخصائص الزائدة» — فهو لا يسمح بوجود خصائص غير موجودة في النوع المستهدف:

TYPESCRIPT
interface Point {
  x: number;
  y: number;
}

// ❌ Check for Unnecessary Attributes——No additional properties are allowed when directly assigning an object literal.
// let p: Point = { x: 1, y: 2, z: 3 };

// ✅ Method 1:Bypassing via Variables(Because variables do not trigger unnecessary property checks)
let temp = { x: 1, y: 2, z: 3 };
let p: Point = temp;   // ✅

// ✅ Method 2:Using Type Assertions
let p2: Point = { x: 1, y: 2, z: 3 } as Point;

// ✅ Method 3:Extension Interface(Top Recommendations)
interface Point3D extends Point {
  z: number;
}
let p3: Point3D = { x: 1, y: 2, z: 3 };
💡 لماذا يجب التحقق من وجود خصائص إضافية؟ الغرض من ذلك هو اكتشاف الأخطاء الإملائية. إذا كتبت { x: 1, y: 2, zzz: 3 }، فسوف ينبهك TypeScript على الفور — zzz ليست خاصية لـ Point؛ هل أخطأت في تهجئتها؟

▶ مثال: الأنواع المنظمة في الممارسة العملية

TYPESCRIPT
interface Printable {
  toString(): string;
}

// Date has toString method,Structural Matching
let date: Printable = new Date();

// Custom objects include toString Methods,Also matches
let custom: Printable = {
  toString() { return "Custom Objects"; }
};

function print(obj: Printable): void {
  console.log(obj.toString());
}

print(date);    // Output Date the string representation of
print(custom);  // Output "Custom Objects"
▶ جرّب الكود

الناتج:

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


❓ أسئلة شائعة

س هل ينبغي أن تستخدم أنواع الكائنات الأسماء المستعارة interface أم type؟
ج في معظم الحالات، كلاهما متكافئ. يُعد interface أكثر ملاءمةً لتعريف أشكال الكائنات (حيث يدعم دمج الإعلانات والوراثة extends)، بينما يُعد type أكثر ملاءمةً للعمليات المعقدة مثل أنواع الاتحاد والأنواع الشرطية. طالما أن فريقك يتبع نهجًا متسقًا، فلا بأس بذلك. سترد مقارنة تفصيلية لاحقًا في هذا الفصل. يُنصح المبتدئون بالبدء باستخدام interface.
س ما الفرق بين الخصائص الاختيارية ونوع الاتحاد undefined؟
ج يسمح age?: number بغياب الخصائص (غيابها تمامًا)، بينما يشترط age: number | undefined وجود الخصائص، لكن قيمها يمكن أن تكون undefined. في التطوير الفعلي، تُستخدم الخصائص الاختيارية بشكل أكثر شيوعًا — حيث يبدو حذف خاصية ما أكثر طبيعية من كتابة undefined صراحةً.
س لماذا تخضع القيم الحرفية للكائنات لفحوصات إضافية على الخصائص، بينما لا تخضع المتغيرات لذلك؟
ج عند تعيين القيم مباشرةً إلى القيم الحرفية للكائنات، عادةً ما يكون المطورون على دراية تامة بما كتبوه — فالخصائص الزائدة غالبًا ما تكون أخطاءً مطبعية. أما عند تعيين القيم للمتغيرات، فقد يأتي نوع المتغير من مصدر آخر، وقد تكون الخصائص الزائدة صالحة (مثل الخصائص الإضافية من نوع فرعي). وهذا هو التوازن الذي تحققه TypeScript بين «الأمان» و«المرونة».
س هل القيم التي تحمل السمة readonly غير قابلة للتغيير حقًّا؟
ج readonly هي مجرد فحص يتم في مرحلة الترجمة؛ ولا توجد حماية في مرحلة التشغيل. ويمكن تجاوزها باستخدام تأكيدات الأنواع أو النوع any. ومع ذلك، فإنها تمنع بشكل فعال «التعديلات العرضية» — وهو الغرض الأساسي لنظام الأنواع: اكتشاف الأخطاء في وقت التحويل البرمجي، بدلاً من توفير الحماية في وقت التشغيل.

📖 ملخص

📝 تمارين

  1. المسألة الأساسية (الصعوبة ⭐): عرّف نوع كائن لتمثيل «كتاب» (العنوان، المؤلف، السعر، متوفر؟)، وأنشئ كائنين يمثلان كتابين — أحدهما متوفر والآخر غير متوفر — واطبع عناوينهما وأسعارهما.
  2. مشكلة متقدمة (درجة الصعوبة ⭐⭐): اكتب دالة updateUser(user: { name: string; age: number; email?: string }, updates: { age?: number; email?: string }) تُرجع كائن المستخدم المُحدَّث. يجب أن يكون كائن المستخدم الأصلي name غير قابل للتغيير (استخدم readonly).
  3. التحدي (الصعوبة ⭐⭐⭐): عرّف نوعًا CacheStore باستخدام توقيعات الفهرس، بحيث يكون المفتاح سلسلة نصية والقيمة هي { data: T; timestamp: number } (حيث T هو نوع عام). أنشئ مثيلًا لـ CacheStore<string>، وقم بتخزين إدخالين في ذاكرة التخزين المؤقتة فيه، ثم قم بالتكرار وعرض جميع المفاتيح المخزنة في ذاكرة التخزين المؤقتة وحالات انتهاء صلاحيتها (بافتراض أن مدة الصلاحية هي 5 ثوانٍ).
Web-Tutorial.com

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

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

100%