TypeScript: أساسيات العناصر العامة في TypeScript

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

تعد «الأنواع العامة» أقوى ميزة في لغة TypeScript — فهي تتيح لك كتابة كود «يعمل مع أنواع متعددة» مع الحفاظ على أمان الأنواع الكامل. ويُعد فهم «الأنواع العامة» خطوة أساسية في إتقان لغة TypeScript.

1. لماذا نحتاج إلى الأدوية الجنيسة؟

(1) معضلة عدم وجود الأدوية الجنيسة

TYPESCRIPT
// Option 1: Use any — missing type information
function firstAny(arr: any[]): any {
  return arr[0];
}

let n = firstAny([1, 2, 3]);    // n is any — don't know if it's number
let s = firstAny(["a", "b"]);   // s is any — don't know if it's string

// Option 2:Write a function for each type——Code Duplication
function firstNumber(arr: number[]): number { return arr[0]; }
function firstString(arr: string[]): string { return arr[0]; }
// Endless...

(2) الحلول العامة

TYPESCRIPT
function first<T>(arr: T[]): T {
  return arr[0];
}

let n = first([1, 2, 3]);       // T Inferred as number → Back number
let s = first(["a", "b"]);      // T Inferred as string → Back string
let b = first([true, false]);   // T Inferred as boolean → Back boolean

console.log(n.toFixed(2));      // ✅ TypeScript knows n is number
console.log(s.toUpperCase());   // ✅ TypeScript knows s is string
📌 المفهوم الأساسي: <T> هو معلمة نوع — ويعتمد تعريف T على نوع الحجة التي يتم تمريرها عند استدعاء الدالة. يتم تعريفها مرة واحدة، لكن يمكن استخدامها مع أنواع متعددة، وتتوفر معلومات دقيقة عن النوع في كل حالة.



2. الدوال العامة

(1) قواعد النحو الأساسية

TYPESCRIPT
function identity<T>(value: T): T {
  return value;
}

// TypeScript Automatic Inference T
let num = identity(42);       // T = number
let str = identity("hello");  // T = string

// You can also specify it explicitly T(Sometimes it is necessary to)
let result = identity<string>("hello");

(2) معلمات الأنواع المتعددة

TYPESCRIPT
function pair<A, B>(first: A, second: B): [A, B] {
  return [first, second];
}

let p1 = pair("name", 42);        // [string, number]
let p2 = pair(true, [1, 2, 3]);   // [boolean, number[]]

(3) الأنواع العامة والمصفوفات

TYPESCRIPT
function map<T, U>(arr: T[], transform: (item: T) => U): U[] {
  return arr.map(transform);
}

let numbers = [1, 2, 3];
let strings = map(numbers, n => `No.${n}`);
// T = number, U = string → Back string[]

console.log(strings);  // ["No.1", "No.2", "No.3"]

▶ مثال: وظائف المساعدة العامة

TYPESCRIPT
// Filter Array
function filter<T>(arr: T[], predicate: (item: T) => boolean): T[] {
  return arr.filter(predicate);
}

// Searching an Array
function find<T>(arr: T[], predicate: (item: T) => boolean): T | undefined {
  return arr.find(predicate);
}

// Array Chunking
function chunk<T>(arr: T[], size: number): T[][] {
  let result: T[][] = [];
  for (let i = 0; i < arr.length; i += size) {
    result.push(arr.slice(i, i + size));
  }
  return result;
}

let nums = [1, 2, 3, 4, 5, 6, 7];
let evens = filter(nums, n => n % 2 === 0);
let found = find(nums, n => n > 5);
let groups = chunk(nums, 3);

console.log("Even number:" + evens);        // "Even number:2,4,6"
console.log("Found:" + found);        // "Found:6"
console.log("Chunking:" + JSON.stringify(groups));  // "[[1,2,3],[4,5,6],[7]]"
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
Even number:2,4,6
Found:6
Chunking:[[1,2,3],[4,5,6],[7]]


3. الواجهات العامة

(1) قواعد النحو الأساسية

TYPESCRIPT
interface Box<T> {
  value: T;
}

let stringBox: Box<string> = { value: "hello" };
let numberBox: Box<number> = { value: 42 };

(2) وظيفة وصف الواجهة العامة

TYPESCRIPT
interface Transformer<A, B> {
  (input: A): B;
}

let toString: Transformer<number, string> = (n) => String(n);
let toLength: Transformer<string, number> = (s) => s.length;

console.log(toString(42));       // "42"
console.log(toLength("hello"));  // 5

(3) نمط مستودع وصف الواجهة العامة

TYPESCRIPT
interface Repository<T> {
  findById(id: string): T | null;
  findAll(): T[];
  save(item: T): void;
  delete(id: string): boolean;
}

interface User {
  id: string;
  name: string;
}

class UserRepo implements Repository<User> {
  private data: User[] = [];

  findById(id: string): User | null {
    return this.data.find(u => u.id === id) ?? null;
  }

  findAll(): User[] {
    return this.data;
  }

  save(item: User): void {
    this.data.push(item);
  }

  delete(id: string): boolean {
    let len = this.data.length;
    this.data = this.data.filter(u => u.id !== id);
    return this.data.length < len;
  }
}


4. الفئات العامة

(1) قواعد النحو الأساسية

TYPESCRIPT
class Stack<T> {
  private items: T[] = [];

  push(item: T): void {
    this.items.push(item);
  }

  pop(): T | undefined {
    return this.items.pop();
  }

  peek(): T | undefined {
    return this.items[this.items.length - 1];
  }

  get size(): number {
    return this.items.length;
  }
}

let numStack = new Stack<number>();
numStack.push(1);
numStack.push(2);
numStack.push(3);
console.log(numStack.pop());   // 3
console.log(numStack.peek());  // 2
console.log(numStack.size);    // 2

let strStack = new Stack<string>();
strStack.push("a");
strStack.push("b");
console.log(strStack.pop());   // "b"

(2) العناصر الثابتة في الفئات العامة

TYPESCRIPT
class GenericClass<T> {
  // static defaultValue: T;  // ❌ Static members cannot reference a class's type parameters

  // ✅ Static methods require their own type parameters.
  static create<U>(value: U): GenericClass<U> {
    let instance = new GenericClass<U>(value);
    return instance;
  }

  constructor(public value: T) {}
}

let instance = GenericClass.create("hello");  // GenericClass<string>
📌 السبب: معلمة النوع الخاصة بالفئة T هي على مستوى المثيل — حيث يمكن أن يكون لكل مثيل معلمة T مختلفة. أما العناصر الثابتة فتنتمي إلى الفئة نفسها، وليس إلى أي مثيل محدد، لذا لا يمكنها الإشارة إلى معلمات النوع الخاصة بأي مثيل.



5. القيود العامة (extends)

بشكل افتراضي، يمكن أن يكون النوع العام T أي نوع — وقد يكون هذا النطاق واسعًا جدًّا في بعض الأحيان. استخدم extends لتقييد T بحيث يجب أن يستوفي شروطًا معينة:

(1) مقيد بواجهة معينة

TYPESCRIPT
interface HasLength {
  length: number;
}

// T Must have length Properties
function logLength<T extends HasLength>(value: T): void {
  console.log(`Length:${value.length}`);
}

logLength("hello");        // ✅ string has length
logLength([1, 2, 3]);      // ✅ The array contains length
logLength({ length: 10 }); // ✅ Objects include length
// logLength(42);          // ❌ number None length

(2) القيد هو نوع عام آخر

TYPESCRIPT
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

let user = { name: "Charlie", age: 20, email: "xiao@example.com" };

console.log(getProperty(user, "name"));   // ✅ "Charlie" —— K = "name"
console.log(getProperty(user, "age"));    // ✅ 20 —— K = "age"
// getProperty(user, "phone");            // ❌ "phone" No user the key

(3) القيود كعناصر بناء

TYPESCRIPT
function createInstance<T>(Constructor: new () => T): T {
  return new Constructor();
}

class Dog {
  bark() { return "Woof!"; }
}

let dog = createInstance(Dog);
console.log(dog.bark());  // "Woof!"

▶ مثال: دالة عامة مع قيد keyof

TYPESCRIPT
function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K> {
  let result = {} as Pick<T, K>;
  for (let key of keys) {
    result[key] = obj[key];
  }
  return result;
}

let user = { id: 1, name: "Charlie", email: "c@example.com", age: 25 };

let summary = pick(user, "id", "name");
// Type: { id: number; name: string }
console.log(summary.name);  // "Charlie"
// summary.email;           // ❌ email was not picked
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
Charlie

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

TYPESCRIPT
function merge<T extends object, U extends object>(a: T, b: U): T & U {
  return { ...a, ...b };
}

let defaults = { host: "localhost", port: 3000 };
let custom = { port: 8080, debug: true };

let config = merge(defaults, custom);
// Inference Types: { host: string; port: number } & { port: number; debug: boolean }
// After simplification: { host: string; port: number; debug: boolean }

console.log(config.host);   // "localhost"
console.log(config.port);   // 8080
console.log(config.debug);  // true
▶ جرّب الكود

الناتج:

TEXT 📖 للعرض فقط
localhost
8080
true



6. الأنواع العامة المدمجة الشائعة

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

النوع الوظيفة مثال
Array<T> نوع المصفوفة Array<number>
Promise<T> نوع الوعد Promise&lt;string&gt;
Record<K, V> كائن زوج المفتاح-القيمة Record<string, number>
Partial<T> جميع السمات اختيارية Partial<Config>
Required<T> جميع الحقول إلزامية Required<Config>
Readonly<T> جميع الخصائص هي للقراءة فقط Readonly<Config>
Pick<T, K> تحديد سمات معينة Pick<User, "name" | "age">
Omit<T, K> استبعاد سمات معينة Omit<User, "email">
Exclude<T, U> استبعاد من أنواع الاتحاد Exclude<"a"|"b"|"c", "a">
Extract<T, U> مقتطف من «أنواع الاتحاد» Extract<"a"|"b"|"c", "a"|"b">
ReturnType<T> نوع قيمة الإرجاع للدالة ReturnType<typeof fn>
Parameters<T> مجموعة من أنواع معلمات الدالة Parameters<typeof fn>
TYPESCRIPT
// Examples of Actual Use
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

// When creating a user id Not required
type CreateUser = Omit<User, "id">;

// All fields are optional when updating a user
type UpdateUser = Partial<User>;

// The user summary includes only some fields
type UserSummary = Pick<User, "id" | "name">;

let newUser: CreateUser = { name: "Charlie", email: "xiao@example.com", age: 20 };
let update: UpdateUser = { age: 21 };
let summary: UserSummary = { id: 1, name: "Charlie" };

❓ أسئلة شائعة

س كيف ينبغي تسمية معلمات الأنواع العامة؟
ج وفقًا للأعراف المتبعة، استخدم حرفًا واحدًا كبيرًا — T (النوع)، U/V/W (الأنواع التالية)، K (المفتاح)، V (القيمة)، E (العنصر). في حالة الكلمات المتعددة، استخدم أسلوب PascalCase: TItem، TResponse. يجب على الفرق اعتماد قاعدة تسمية متسقة.
س متى تحتاج إلى تحديد نوع عام بشكل صريح؟
ج في معظم الحالات، يمكن لـ TypeScript استنتاج الأنواع العامة من المعلمات. وتحتاج إلى تحديدها بشكل صريح في الحالات التالية: (1) عندما لا تحتوي الدالة على معلمات، أو عندما لا تسمح أنواع المعلمات بتحديد T؛ (2) عندما يكون الاستنتاج واسعًا جدًّا (على سبيل المثال، عندما يُستنتج على أنه نوع اتحاد)؛ (3) تحتاج إلى تحكم أكثر دقة في نوع القيمة المرجعة للدالة.
س ما الفرق بين القيد العام extends ووراثة الفئات extends؟
ج الصيغة النحوية واحدة، لكن المعنى يختلف. القيد العام T extends HasLength يعني أن «T يجب أن يكون له على الأقل بنية HasLength» — وهي علاقة توافق بين الأنواع. أما وراثة الفئة class Dog extends Animal فتعني أن «Dog هي فئة فرعية من Animal» — وهي علاقة موجهة للكائنات.
س ما الفرق بين Record<string, number> و{ [key: string]: number }؟
ج إنهما متطابقان تمامًا. Record هو نوع أداة عامة مدمج يمثل في الأساس توقيع فهرس. Record<string, number> أكثر إيجازًا، بينما { [key: string]: number } أكثر مرونة (فهو يسمح لك بإضافة خصائص معروفة).

📖 ملخص

📝 تمارين

  1. المشكلة الأساسية (صعوبة ⭐): اكتب دالة عامة wrapInArray<T>(value: T): T[] تقوم بتحويل قيمة واحدة إلى مصفوفة. اختبرها باستخدام أنواع number وstring.
  2. مشكلة متقدمة (درجة الصعوبة ⭐⭐): اكتب دالة عامة pluck<T, K extends keyof T>(items: T[], key: K): T[K][] تستخرج قيمة خاصية محددة من مصفوفة من الكائنات. على سبيل المثال، تُرجع الدالة pluck([{name: "a", age: 1}, {name: "b", age: 2}], "name") القيمة ["a", "b"].
  3. التحدي (الصعوبة: ⭐⭐⭐): قم بتنفيذ فئة عامة DataStore<T extends { id: string }> توفر طرق الحفظ والبحث والحذف. تأكد من وجود الخاصية id من خلال قيود التعميم. أنشئ مثيلًا لفئة DataStore<{ id: string; title: string }> لاختبار وظائف CRUD.
Web-Tutorial.com

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

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

100%