آموزش جنریک در تایپ اسکریپت [Generics in TypeScript]
![آموزش جنریک در تایپ اسکریپت [Generics in TypeScript]](https://rahkarino.ir/wp-content/uploads/2023/03/rahkarino-featuredImg-new-1.jpg)
درود بر دوستان عزیز. در مقاله قبل، تایپ اسکریپت را معرفی کردیم و به بررسی انواع تایپ های آن پرداختیم. قبل از اینکه این مقاله را بخوانید پیشنهاد می کنم ابتدا مقاله آموزشی تایپ اسکریپت را مطالعه کنید.
در این مقاله می خواهیم یکی از مباحث مهم و کاربردی زبان برنامه نویسی تایپ اسکریپت را با عنوان جنریک تایپ ها (Generic types) بررسی کنیم.
این مقاله آموزشی موارد زیر را شامل می شود:
Generic classes, Generic functions, Generic interfaces, Generic constraints, Type mapping
Generic Classes:
مواردی وجود دارد که باید تایپ را در هنگام فراخوانی تابع یا نمونه ساختن از یک کلاس تعریف کنیم. به عنوان مثال در کلاس زیر، که متد سازنده آن یک key و value می گیرد، اگر نوع key یا value را برابر یک مقدار خاص مثلا number قرار دهیم، هنگام ساختن instance از این کلاس فقط می توانیم عدد پاس دهیم. اما توسط جنریک تایپ ها (Generics) می توان نوع key یا value را هنگام نمونه ساختن از کلاس تعیین کنیم.
class KeyValuePair<K, V> { constructor(public key: K, public value: V) {} } let pair = new KeyValuePair<number, string>(1, "a"
در تصویر زیر key از نوع عددی و value از نوع رشته ای می باشد. پس وقتی pair.key را می نویسیم به تمام متدهای مربوط به اعداد دسترسی خواهیم داشت:
نکته: در تصویر فوق در خط 104 نیازی نیست تایپ key و value را بصورت <number, string> تعریف کنیم زیرا خود تایپ اسکریپت وقتی مقادیر 1, “a” را مشاهده کند می فهمد نوع داده ای key, value چیست.
Generic Functions:
همانطور که در کلاس ها توانستیم از Generic Type ها استفاده کنیم، در توابع نیز این قابلیت را داریم. یعنی هنگام فراخوانی تابع، نوع آرگومان ورودی را تعریف کنیم. مثال زیر:
function getValue<T>(value: T) { return value; } let value1 = getValue(1); let value2 = getValue("a");
Generic Interfaces:
همانند کلاس و تابع، برای interface هم می توان از Generic type ها استفاده کرد. فرض کنید در کد زیر، می خواهیم نتایج حاصل از دو درخواست به سرور را نمایش دهیم. یکی از این درخواست ها برای دریافت کاربران (Users) است و دیگری برای دریافت محصولات (Products). در Result نوع data را برابر T یا null تعریف کردیم. زیرا ممکن است نتیجه ای از سرور برنگردد. T هم بر اساس نوع درخواست تعیین می شود. اگر به User API درخواست زده باشیم T برابر User می شود که یک فیلد username دارد.
کد زیر را در نظر بگیرید:
// APIs: // http://www.mywebsite.com/users // http://www.mywebsite.com/products interface Result<T> { data: T | null; error: string | null; } interface User { username: string; } interface Product { title: string; } function fetch<T>(url: string): Result<T> { return { data: null, error: null }; } let result1 = fetch<User>("url"); let data1 = result1.data?.username; let result2 = fetch<Product>("url"); let data2 = result2.data?.title;
Generic Constraints:
می توان جنریک تایپ را محدود کرد. مثلا به یک کلاس، interface یا… برای درک قضیه به مثال زیر توجه کنید:
interface Person { name: string; } function echo<T extends Person>(value: T): T { return value; } echo({ name: "a" });
مشاهده می کنید که نوع جنریک T در تابع echo به اینترفیس Person محدود شود. یعنی وقتی می خواهیم تابع echo را فراخوانی کنیم، باید نوع آرگومان ورودی، حتما شامل name از نوع رشته باشد. دقت کنید که علاوه بر name می تواند پراپرتی های دیگری نیز داشته باشد. مانند کد زیر:
echo({ name: "a", age: 20 });
Type Mapping:
در برخی مواقع نیاز داریم یک type را بر اساس تایپ دیگر تعریف کنیم. فرض کنید اینترفیس زیر را داریم:
interface Product { name: string; price: number; }
حالا می خواهیم برای بخش دیگر از برنامه خود از تایپ product استفاده کنیم با این تفاوت که تمام فیلدهای آن باید readonly باشند. یک روش اینست که مانند کد زیر یک تایپ دیگر بسازیم و کلمه readonly را برای هریک از پراپرتی های آن تعریف کنیم:
interface Product2 { readonly name: string; readonly price: number; }
اینکار اصلا بهینه نیست و توصیه نمی شود. زیرا هم کد تکراری داریم (قانون DRY: Don’t Repeat Yourself رعایت نشده است) و هم اگر یک پراپرتی جدید به Product اضافه شود باید حواسمان باشد که به Product2 نیز آن را اضافه کنیم. که دردسر ساز خواهد بود.
روش اصولی انجام اینکار استفاده از Type Mapping در تایپ اسکریپت می باشد. در کد زیر، یک تایپ جدید تعریف کرده ایم بنام ReadOnlyProduct که در یک حلقه، تمام key, value های نوع Product را داریم و هر یک از آنها را به شکل readonly تعریف کرده ایم:
type ReadOnlyProduct = { readonly [K in keyof Product]: Product[K]; };
اکنون اگر یک متغیر از نوع بالا بسازیم، نمی توانیم مقادیر name و price را تغییر دهیم. زیرا readonly هستند:
let product: ReadOnlyProduct = { name: "Shoe", price: 230, }; product.name = "test"; // this line has error
کد فوق یک محدودیت دارد و آن اینست که خاصیت readonly بودن فقط مختص نوع Product است. ما می توانیم آن را با Generic type ها ترکیب کنیم و یک نوع داینامیک تری بسازیم. کد زیر را در نظر بگیرید:
type ReadOnly<T> = { readonly [K in keyof T]: T[K]; }; let product: ReadOnly<Product> = { name: "a", price: 100, };
در این کد، نوع ReadOnly بصورت عمومی و جنریک تعریف شده است و می توانیم Product یا Customer یا … را برای آن در نظر بگیریم.
به همین ترتیب می توانیم قابلیت null بودن و یا optional بودن را به شکل زیر تعریف کنیم:
type Optional<T> = { [K in keyof T]?: T[K]; }; type Nullable<T> = { [K in keyof T]?: T[K] | null; };
نکته: برای مطالعه بیشتر در زمینه این قابلیت در تایپ اسکریپت به سایت typescriptlang مراجعه کنید.
دیدگاهتان را بنویسید