چگونه از جنریک‌ها در TypeScript استفاده کنیم؟

جنریک‌ها در تایپ‌اسکریپت

جنریک‌ها یکی از ویژگی‌های اساسی زبان‌های نوع استاتیک هستند که به توسعه‌دهندگان اجازه می‌دهند تا نوع‌ها را به عنوان پارامتر به یک نوع، تابع یا ساختارهای دیگر ارسال کنند. وقتی کامپوننتی جنریک شود، این امکان را می‌دهد که تایپ‌ها را در زمان استفاده اعمال کند که باعث انعطاف‌پذیری بیشتر کد، قابلیت استفاده مجدد و کاهش تکرار می‌شود.

تایپ‌اسکریپت از جنریک‌ها به طور کامل پشتیبانی می‌کند تا نوع‌های ایمن‌تری برای کامپوننت‌هایی که آرگومان یا مقدار بازگشتی با نوع نامشخص دارند، ارائه دهد. در این آموزش، با مثال‌های واقعی از جنریک‌ها در توابع، تایپ‌ها، کلاس‌ها و اینترفیس‌ها آشنا می‌شوید و همچنین با استفاده از جنریک‌ها، نوع‌های مپ‌شده و مشروط ساخته می‌شوند که به شما در ایجاد کامپوننت‌های منعطف و کاربردی کمک می‌کنند.

پیش‌نیاز آموزش

تمام مثال‌ها در این آموزش با استفاده از نسخه 4.2.3 تایپ‌اسکریپت نوشته شده‌اند.

نحو استفاده از جنریک‌ها

جنریک‌ها در تایپ‌اسکریپت داخل پرانتز زاویه‌دار به شکل <T> نوشته می‌شوند که T نمایانگر نوع پاس داده شده است. T همانند پارامتر توابع یک جای‌خالی برای نوع در نظر گرفته می‌شود که هنگام ساخت نمونه مشخص می‌شود. چندین نوع جنریک می‌توانند به طور همزمان قرار بگیرند، مانند <T, K, A>.

توجه: به طور رایج از یک حرف برای نام جنریک‌ها استفاده می‌شود، هرچند این یک قانون نیست.

مثال ساده با تابع

تابعی می‌خواهیم که دو پارامتر می‌گیرد: یک شیء و آرایه‌ای از کلیدها، و یک شیء جدید با تنها کلیدهای دلخواه می‌سازد:

function pickObjectKeys(obj, keys) {
  const newObj = {};

  keys.forEach((key) => {
    newObj[key] = obj[key];
  });
  return newObj;
}

مثال استفاده از تابع:

const language = {
  name: "TypeScript",
  age: 10,
  extensions: ["ts", "tsx"],
};

const ageAndExtensions = pickObjectKeys(language, ["age", "extensions"]);

نتیجه:

{
  age: 10,
  extensions: ["ts", "tsx"],
}

برای تبدیل این تابع به تایپ‌اسکریپت با اطمینان نوع، از جنریک‌ها استفاده می‌کنیم:

function pickObjectKeys<T extends object, K extends keyof T>(
  obj: T,
  keys: K[]
): Pick<T, K> {
  const newObj = {} as Pick<T, K>;
  keys.forEach((key) => {
    newObj[key] = obj[key];
  });
  return newObj;
}

<T, K extends keyof T> دو پارامتر نوع تعریف می‌کند. K محدود به کلیدهای T است. آرگومان‌ها و مقدار بازگشتی تابع با این تعریف منطبق می‌شوند. این باعث می‌شود IDE مثل Visual Studio Code هنگام تایپ پیشنهادهای مناسب برای کلیدها بدهد.

استفاده از جنریک در توابع

تابع identity که مقداری را می‌گیرد و همان را باز می‌گرداند:

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

در استفاده:

const result = identity(123);

در اینجا result تایپ 123 (عدد لیتیرا) دارد که از فراخوانی نتیجه گرفته شده است. همچنین می‌توان صریح نوع را تعیین کرد:

const result = identity<number>(123);

استفاده از نوع‌های سفارشی هم ممکن است:

type ProgrammingLanguage = { name: string; };

const result = identity<ProgrammingLanguage>({ name: "TypeScript" });

توابع async و جنریک

تابعی برای فراخوانی API با جنریک:

async function fetchApi<ResultType>(url: string): Promise<ResultType> {
  const response = await fetch(url);
  return response.json();
}

چون جنریک در آرگومان درج نشده، فراخواننده fetchApi باید نوع را مشخص کند:

type User = { id: number; name: string; };

const data = await fetchApi<User[]>("/users");

می‌توان به جنریک مقدار پیش‌فرض هم داد، مثل:

async function fetchApi<ResultType = Record<string, any>>(url: string): Promise<ResultType>

محدود کردن نوع جنریک با extends

تابعی که همه مقدارهای شیء را به رشته تبدیل می‌کند:

function stringifyObjectKeyValues<T extends Record<string, any>>(obj: T): { [K in keyof T]: string } {
  return Object.keys(obj).reduce((acc, key) => {
    acc[key as keyof T] = String(obj[key as keyof T]);
    return acc;
  }, {} as { [K in keyof T]: string });
}

اینجا T extends Record<string, any> تضمین می‌کند که obj یک شیء باشد.

جنریک در اینترفیس‌ها و کلاس‌ها

ایجاد اینترفیس جنریک:

interface ApiResponse<T> {
  data: T;
}

کلاس جنریک:

class HttpApplication<T> {
  context: T;

  constructor(context: T) {
    this.context = context;
  }

  get(handler: (ctx: T) => void) {
    handler(this.context);
  }
}

ساخت تایپ‌های سفارشی با جنریک

یک مثال ساده برای درک استفاده:

type Identity<T> = T;

type A = Identity<number>; // نوع number

مثال Partial که تمام فیلدها را اختیاری می‌کند:

type Partial<T> = {
  [P in keyof T]?: T[P];
};

مثال پیشرفته: نوع کمکی برای هزینه ارسال در شبکه فروشگاهی

نوع کمکی IfSameKeyThanParentTOtherwiseOtherType به این شکل کار می‌کند که اگر کلید برابر باشد، نوع والد استفاده می‌شود وگرنه نوع دیگر. این باعث اطمینان از شکل درست مقدار در آدرس‌های ارسال می‌شود.

تایپ مپ شده (Mapped Types) با جنریک

مثلاً می‌خواهیم نوعی بسازیم که تمام خصوصیات تایپ دیگر را بولین کند:

type BooleanFields<T> = { [K in keyof T]: boolean; };

مثال استفاده به صورت گزینه‌هایی که نشان می‌دهند چه فیلدهایی باید گرفته شوند:

type UserFetchOptions = BooleanFields<User>;

مثال نوع Readonly که تمام خصوصیات را فقط‌خواندنی می‌کند:

type Readonly<T> = {
  readonly [K in keyof T]: T[K];
};

تایپ‌های شرطی (Conditional Types)

نوعی که شرط می‌گذارد:

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

برای انواع استرینگ می‌شود true و بقیه false.

استفاده از infer در تایپ شرطی

پیدا کردن نوع بازگشتی تابع:

type GetReturnType<T> = T extends (...args: any[]) => infer U ? U : never;

مثال:

function someFunction() {
  return true;
}

type ReturnTypeOfSomeFunction = GetReturnType<typeof someFunction>;

در اینجا نوع صحیح boolean شناسایی می‌شود.

ساخت نوع شرطی NestedOmit برای حذف کلیدهای داخلی به صورت dot notation

نوعی که بتوانیم با dot notation فیلدهای تو در تو را حذف کنیم:

type NestedOmit<T extends Record<string, any>, KeysToOmit extends string> =
  KeysToOmit extends `${infer KeyPart1}.${infer KeyPart2}`
    ? KeyPart1 extends keyof T
      ? Omit<T, KeyPart1> & { [NewKeys in KeyPart1]: NestedOmit<T[NewKeys], KeyPart2> }
      : T
    : Omit<T, KeysToOmit>;

مثال استفاده:

type NestedObject = {
  a: {
    b: {
      c: string;
      d: number;
    };
    e: boolean;
  };
  f: string;
};

type Result = NestedOmit<NestedObject, "a.b.c">;

در نتیجه فیلد c از ساختار تو در تو حذف می‌شود.

نتیجه‌گیری

در این آموزش با جنریک‌ها در توابع، اینترفیس‌ها، کلاس‌ها، نوع‌های سفارشی، مپ‌تایپ‌ها و تایپ‌های شرطی آشنا شدید. استفاده درست از این قابلیت‌ها نه تنها کد شما را منعطف‌تر می‌کند بلکه از تکرار جلوگیری می‌کند و تجربه توسعه‌دهنده را بهتر می‌سازد.

از همراهی شما با پارمین کلود متشکریم.

Click to rate this post!
[Total: 0 Average: 0]

نظرات کاربران

دیدگاهی بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *