برنامه نویسی

بهترین راه برای استفاده از “Generics” در تایپ اسکریپت

Generics در Typescript به شما امکان می دهد کامپوننتی ایجاد کنید که بتواند با انواع مختلف کار کند. این به کاربران امکان می دهد از این اجزا با نوع خاص خود استفاده کنند.

قبل از خواندن این مقاله، لطفاً مقاله من در مورد انواع ابزارهای TS را بخوانید زیرا به شما در درک بهتر این مقاله کمک می کند.

فهرست مطالب

در اینجا چند راه ممکن برای استفاده وجود دارد Generics در تایپ اسکریپت:

1. ژنریک با نوع

ساده ترین راه استفاده generics با … است type. مثلا:

type MyData<Type> = {
  data: Type
}

type DataAsString =  MyData<string>;
// type DataAsString = {
//    data: string;
// }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

چون گذشتیم string به عنوان یک پارامتر نوع، داده های ما خواهد بود string نوع به همین ترتیب، اگر ما از number نوع، داده های ما از نوع عددی خواهد بود. و این زیبایی است generics. شما می توانید با هر نوع کار کنید.

type MyData<Type> = {
  data: Type
}

type DataAsString =  MyData<number>;
// type DataAsString = {
//    data: number;
// }
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

2. توابع عمومی

همانطور که می توانید آرگومان ها را به یک تابع ارسال کنید، همچنین می توانید ارسال کنید type arguments به یک تابع مثلا:

const fetchData = <Type>(url: string): Promise<Type> => {
    return fetch(url)
}

fetchData<{name: string, age: number}>("/api/books")
  .then(res => {
      console.log(res)
      // res: {name: string, age: number}
  })
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

گذشتیم {name: string, age: number} به عنوان یک type argument به fetchData تابع، و این نوع آرگومان می گوید res آنچه قرار است باشد زیرا این چیزی است که در حال تایپ شدن است Promise<Type>. بنابراین در حال حاضر تابع fetchData به یک تابع عمومی تبدیل شده است. یک تابع عمومی فقط یک تابع عادی است اما شامل یک آرگومان نوع است.

اگر بخواهیم نوع دیگری را پاس کنیم، ما res همان نوع را تکرار خواهد کرد.

const fetchData = <Type>(url: string): Promise<Type> => {
    return fetch(url)
}

fetchData<{id: number, email: string}>("/api/books")
  .then(res => {
      console.log(res)
      // res: {id: number, email: string}
  })
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

3. ژنریک با توابع داخلی

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

const numberSet = new Set<number>();
numberSet.add(1);
numberSet.add(2);
numberSet.add(3);
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

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

const stringToNumberMap = new Map<string, number>();
stringToNumberMap.set("one", 1);
stringToNumberMap.set("two", 2);
stringToNumberMap.set("three", 3);
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

4. استنباط انواع

اگر شما type argument شبیه شماست runtime argument پس لازم نیست که یک را بگذرانید generic type به عملکرد شما مثلا:

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

const result1 = identity<number>(42); // const result1: number
const result2 = identity<string>("hello"); // const result2: string
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در حالی که مثال بالا درست به نظر می رسد، می توانیم آن را بیشتر ساده کنیم:

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

const result1 = identity(42); // const result1: number
const result2 = identity("hello"); // const result2: string
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در این مورد، زیرا ما از هیچ کدام عبور نمی کنیم type argument، سپس Typescript در نگاه خواهد کرد runtime argument برای دیدن اگر می تواند infer هر چیزی از آن
بنابراین نوع بازگشت است inferred از نوع آرگومان زمان اجرا، و تابع همان نوع دریافتی را برمی گرداند.

5. محدودیت در آرگومان های نوع

در مثال زیر، ما می خواستیم بتوانیم از آن استفاده کنیم ReturnType نوع ابزار، اما کامپایلر نمی تواند ثابت کند که هر نوع یک تابع است. این می تواند یک رشته یا عدد باشد، و همانطور که می دانیم، این است ReturnType فقط با توابع کار می کند، بنابراین به ما هشدار می دهد که نمی توانیم این فرض را بکنیم.

type GetPromiseData<T> = Awaited<ReturnType<T>>
// Error: Type 'T' does not satisfy the constraint '(...args: any) => any'

type PromiseResult = GetPromiseData<
  () => Promise<{
    id: string
    email: string
  }>
>
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بنابراین برای روشن شدن آن برای تایپ اسکریپ، باید این را بگوییم T فقط یک تابع است و ما می توانیم این کار را با اضافه کردن انجام دهیم extends (...args: any) => any بعد از T:

type GetPromiseData<T extends (...args: any) => any> = Awaited<ReturnType<T>>

type PromiseResult = GetPromiseData<
  () => Promise<{
    id: string
    email: string
  }>
>
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

6. محدودیت در توابع

در مثال زیر ما می خواستیم به آن دسترسی داشته باشیم .address خاصیت arg، اما کامپایلر نتوانست ثابت کند که هر نوع دارای a است .address دارایی، بنابراین به ما هشدار داد که نمی توانیم این فرض را داشته باشیم.

function myFunc<Type>(arg: Type): Type {
  console.log(arg.address); // Property 'address' does not exist on type 'Type'.
  return arg;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

برای رفع این خطا، می توانیم یک رابط ایجاد کنیم address اموال در آن و گسترش آن با Type بحث و جدل:

interface IDetails {
  address: string;
}

function myFunc<Type extends IDetails>(arg: Type): Type {
  console.log(arg.address); // Now we know it has a .address property, so no more error
  return arg;
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

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

myFunc(true);
// error: Argument of type 'boolean' is not assignable to parameter of type 'IDetails'.
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در عوض، ما باید مقادیری را که نوع آنها دارد، پاس کنیم address ویژگی:

myFunc({ length: 10, address: "Something" });
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

7. استفاده کنید as در صورت لزوم

گاهی اوقات با استفاده از as بهترین کاری است که می توانید هنگام استفاده از ژنریک انجام دهید. مثلا:

const ObjectKeys = <T extends {}>(obj: T): Array<keyof T> => {
  return Object.keys(obj) // Error: Type 'string[]' is not assignable to type '(keyof T)[]'. 
}

const result = ObjectKeys({
  id: 6,
  email: "me@gmail.com"
})
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا ما یک خطا دریافت می کنیم زیرا Object.keys متد آرایه ای از مقادیر رشته را برمی گرداند و TypeScript نمی تواند تضمین کند که مقادیر رشته برگردانده شده توسط Object.keys در واقع با کلیدهای معتبر از نوع عمومی مطابقت دارد T.

برای رفع این خطا، می‌توانیم به صراحت آن را تایپ کنیم Object.keys منجر به آرایه ای از keyof می شود T با استفاده از as کلمه کلیدی:

const ObjectKeys = <T extends {}>(obj: T) => {
  return Object.keys(obj) as Array<keyof T>
}

const result = ObjectKeys({
  id: 6,
  email: "me@gmail.com"
})
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

8. ژنریک های متعدد

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

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

let x = { a: 1, b: "b", c: true, d: 4 };

getProperty(x, "a"); // return type: string | number | boolean
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در حالی که مثال بالا درست است، مشکل این است که نوع بازگشت صریح نیست. نوع برگشت یک است union متشکل از 3 نوع مختلف برای رفع این مشکل، می توانیم از چندین نوع عمومی استفاده کنیم:

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

let x = { a: 1, b: "b", c: true, d: 4 };

getProperty(x, "a"); // return type: number
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در این مثال، پارامتر نوع عمومی را اضافه کرده ایم Key برای نشان دادن نوع کلید شی T. بنابراین اکنون ما فقط یک نوع بازگشت خاص را دریافت خواهیم کرد.

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

let x = { a: 1, b: "b", c: true, d: 4 };

getProperty(x, "c"); // return type: boolean
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

9. پیش فرض در آرگومان های نوع

ما همچنین می توانیم از انواع پیش فرض در ژنریک استفاده کنیم. به مثال زیر توجه کنید:

const makeSet = <T>() => {
  return new Set<T>()
}

const mySet = makeSet() // const mySet: Set<unknown>
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

اینجا داریم می گیریم unknown زیرا ما هیچ نوع آرگومانی را به آن منتقل نکردیم makeSet تابع. ما می توانیم این مشکل را با ارسال یک آرگومان نوع مانند این حل کنیم، makeSet<number>() یا با تعیین یک نوع پیش فرض:

const makeSet = <T = number>() => {
  return new Set<T>()
}

const mySet = makeSet() // const mySet: Set<number>
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

10. انواع کلاس در Generics

همچنین می توانیم به انواع کلاس ها با توابع سازنده آنها اشاره کنیم. مثلا:

function create<Type>(c: { new (): Type }): Type {
  return new c();
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در اینجا به طور خلاصه نحوه عملکرد این تابع آورده شده است:

  • را create تابع با یک پارامتر type اعلام می شود Type، نشان دهنده نوعی است که تابع خواهد ساخت.

  • تابع یک آرگومان واحد می گیرد c، یک شی که یک تابع سازنده برای نوع را نشان می دهد Type. استدلال نوع دارد { new (): Type }یک نوع شی که یک تابع سازنده را مشخص می کند که هیچ آرگومان نمی گیرد و مقداری از نوع را برمی گرداند Type.

  • را new کلمه کلیدی برای ایجاد یک نمونه جدید از نوع استفاده می شود Type در داخل تابع با فراخوانی تابع سازنده که به عنوان آرگومان ارسال می شود c. را new کلمه کلیدی یک شی دیگر از نوع ایجاد می کند Type و آن را به عنوان نتیجه تابع برمی گرداند.

  • نوع بازگشت تابع به صورت مشخص شده است Type، که تضمین می کند که تابع نمونه ای از نوع مشخص شده توسط پارامتر type را برمی گرداند.

در اینجا یک مثال از چگونگی create تابع را می توان استفاده کرد:

class MyClass {
  constructor(public value: string) {}
}

const instance = create(MyClass);
console.log(instance.value); // Output: undefined
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این مثال یک کلاس ساده را تعریف می کند، MyClass, با یک ارزش دارایی واحد سپس ما تماس می گیریم create تابع، عبور از MyClass تابع سازنده به عنوان آرگومان را create تابع یک نمونه جدید از MyClass از تابع سازنده استفاده می کند و آن را به عنوان نمونه ای از نوع برمی گرداند MyClass.


نتیجه

در نتیجه، پشتیبانی TypeScript از ژنریک ابزار قدرتمندی برای نوشتن کد ایمن و قابل استفاده مجدد از نوع TypeScript فراهم می کند. با تعریف انواع و توابع عمومی، می توانید کدی ایجاد کنید که با انواع مختلف کار می کند و در عین حال بررسی دقیق نوع را انجام می دهد.

برای بهترین استفاده از ژنریک ها در TypeScript، درک نحوه تعریف و استفاده از انواع عمومی، تعیین محدودیت ها در پارامترهای نوع عمومی و استفاده از استنتاج نوع برای کاهش نیاز به حاشیه نویسی نوع صریح ضروری است. علاوه بر این، برای حفظ خوانایی خوب کد و جلوگیری از پیچیدگی های غیر ضروری، استفاده از ژنریک ها بسیار مهم است.

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

بازدید کنید:
👨‍💻کارنامه من
🏞️Fiverr من
🌉گیتهاب من

نوشته های مشابه

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

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

دکمه بازگشت به بالا