بهترین راه برای استفاده از “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 من
🌉گیتهاب من