TypeScript المتقدّم: Generics والأنواع المتقدّمة بأمثلة
لغات البرمجة

TypeScript المتقدّم: Generics والأنواع المتقدّمة بأمثلة

ترقَ من TypeScript المبتدئ إلى المتقدّم — Generics، Utility Types، Conditional Types، و template literals.

م
مؤسس LahbabiGuide
4 دقائق قراءة
شارك:

لماذا Generics؟

Generics تسمح لك بكتابة كود يعمل مع أنواع مختلفة بدون فقدان أمان الأنواع.

مشكلة بدون Generics

ts
// تُعيد كل شيء كـ any — فقدنا النوع!
function firstItem(arr: any[]): any {
  return arr[0];
}

const nums = [1, 2, 3];
const first = firstItem(nums); // first: any ❌

الحلّ بـ Generics

ts
function firstItem<T>(arr: T[]): T {
  return arr[0];
}

const first = firstItem([1, 2, 3]);   // number ✅
const name = firstItem(["أ", "ب"]);   // string ✅

Generics في الدوال

ts
// Generic عام
function identity<T>(value: T): T {
  return value;
}

// مع قيود (extends)
function logLength<T extends { length: number }>(item: T): T {
  console.log(item.length);
  return item;
}

logLength("hello");         // ✅ string له length
logLength([1, 2, 3]);       // ✅ array له length
logLength(42);              // ❌ number ليس له length

Generics في الـ interfaces

ts
interface ApiResponse<T> {
  data: T;
  error: string | null;
  status: number;
}

interface User { id: number; name: string; }
interface Post { id: number; title: string; }

const userRes: ApiResponse<User> = {
  data: { id: 1, name: "أحمد" },
  error: null,
  status: 200,
};

const postRes: ApiResponse<Post[]> = {
  data: [{ id: 1, title: "مرحبا" }],
  error: null,
  status: 200,
};
إعلان

Utility Types الأساسية

TypeScript يأتي بعشرات Utility Types جاهزة:

Partial — كل الحقول اختيارية

ts
interface User {
  id: number;
  name: string;
  email: string;
}

type UserUpdate = Partial<User>;
// { id?: number; name?: string; email?: string; }

function updateUser(id: number, updates: Partial<User>) {
  // ...
}

updateUser(1, { name: "جديد" }); // ✅

Required — عكس Partial

ts
interface Config {
  host?: string;
  port?: number;
}

type StrictConfig = Required<Config>;
// { host: string; port: number; }

Pick — خذ حقولاً محدّدة

ts
type UserPublic = Pick<User, "id" | "name">;
// { id: number; name: string; }

Omit — أزل حقولاً

ts
type UserWithoutEmail = Omit<User, "email">;
// { id: number; name: string; }

Record — كائن بأنواع محدّدة

ts
type Permissions = Record<string, boolean>;
// أيّ string key → boolean

const perms: Permissions = {
  read: true,
  write: false,
  delete: false,
};

// مع قيود صارمة
type Status = "pending" | "active" | "done";
type Counts = Record<Status, number>;
// { pending: number; active: number; done: number; }

ReturnType — استخرج نوع الإرجاع

ts
function getUser() {
  return { id: 1, name: "x" };
}

type User = ReturnType<typeof getUser>;
// { id: number; name: string; }

Conditional Types

"إذا كان X، فالنوع Y، وإلا Z":

ts
type IsString<T> = T extends string ? true : false;

type A = IsString<"hello">;  // true
type B = IsString<number>;   // false

مثال عملي: استخراج نوع المصفوفة

ts
type ElementType<T> = T extends (infer U)[] ? U : T;

type A = ElementType<string[]>;  // string
type B = ElementType<number[]>;  // number
type C = ElementType<boolean>;   // boolean

infer U تستخرج النوع الداخلي.

Mapped Types

إنشاء نوع جديد من نوع موجود:

ts
// اجعل كل الحقول قابلة للقراءة فقط
type Readonly<T> = {
  readonly [K in keyof T]: T[K];
};

// اجعلها nullable
type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

type User = { id: number; name: string; };
type NullableUser = Nullable<User>;
// { id: number | null; name: string | null; }

Template Literal Types

ts
type Greeting = `hello ${string}`;
const g1: Greeting = "hello world";     // ✅
const g2: Greeting = "hi world";        // ❌

// أمثلة أكثر قوّة
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type Endpoint = `/api/${string}`;
type Route = `${HttpMethod} ${Endpoint}`;

const route: Route = "GET /api/users"; // ✅

Discriminated Unions

قوّة TypeScript الحقيقية:

ts
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "rectangle"; width: number; height: number }
  | { kind: "triangle"; base: number; height: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case "circle":
      return Math.PI * shape.radius ** 2;
    case "rectangle":
      return shape.width * shape.height;
    case "triangle":
      return 0.5 * shape.base * shape.height;
  }
}

TypeScript يعرف في كل case أيّ حقول متوفّرة.

مثال حقيقي: API Client type-safe

ts
type Endpoints = {
  "/users": { GET: { response: User[] } };
  "/users/:id": {
    GET: { response: User };
    PUT: { body: Partial<User>; response: User };
    DELETE: { response: void };
  };
};

async function api<
  Path extends keyof Endpoints,
  Method extends keyof Endpoints[Path]
>(
  path: Path,
  method: Method,
  options?: Endpoints[Path][Method] extends { body: infer B } ? { body: B } : {}
): Promise<
  Endpoints[Path][Method] extends { response: infer R } ? R : never
> {
  // ... implementation
}

// استخدام — TypeScript يفرض كل شيء
const users = await api("/users", "GET");
const user = await api("/users/:id", "PUT", { body: { name: "جديد" } });

الأسئلة الشائعة

متى استخدم Generics؟

عند كتابة كود قابل لإعادة الاستخدام مع أنواع مختلفة — helpers، components، data structures.

Generics vs any؟

any يتخلّى عن الأنواع. Generics تحافظ عليها مع الحفاظ على المرونة.

كم Generic parameter يمكن؟

تقنياً لا حدّ، لكن > 3 يصبح صعب القراءة. فكّر في إعادة التصميم.

شارك:
المزيد من لغات البرمجة
اقرأ أيضاً

مقالات ذات صلة