در تایپ اسکریپت به Generics بیایید

در مقاله قبلی خود، به مقدمهای برای ژنریک در تایپ اسکریپت نگاه میکنیم و چند نمونه از نحوه ایجاد یک تابع، کلاس و رابط عمومی را بررسی کردیم. در این مقاله، ما قصد داریم تا عمیقتر به دنیای محصولات ژنریک بپردازیم.
ما قصد داریم به موضوعات پیشرفته تری مانند:
- چند نوع عمومی
- محدود کردن و استفاده از نوع T
- محدودیت ها و رابط های عمومی
- ایجاد اشیاء جدید در ژنریک
بیایید با ژنریک شروع کنیم.
TypeScript از نماد براکت زاویه دار به همراه یک نماد نوع برای نشان دادن استفاده از نحو عمومی استفاده می کند.
بگذارید بگوییم از نوع استفاده می کنید T
به عنوان نوع عمومی برای مشخص کردن اینکه به عنوان یک نوع عمومی استفاده می شود، باید در براکت های زاویه ای پیچیده شود <T>
. برای نشان دادن اینکه این کد در حال جایگزینی نام نوع معمولی با نماد T است.
اجازه دهید از چند نمونه کد برای واضح تر کردن این موضوع استفاده کنیم:
function identity<T>(arg: T) {
console.log(`typeof T is : ${typeof arg}`);
console.log(`value is : ${arg}`)
}
// Usage
identity(1);
identity("string");
identity(true);
identity(() => { });
identity({ id: 1 });
در اینجا، ما تماس می گیریم identity
تابعی با طیف وسیعی از مقادیر (عدد، رشته، بولی).
اگر خروجی را روی کنسول چاپ کنید، به صورت زیر خواهد بود:
value is : 1
typeof T is : string
value is : string
typeof T is : boolean
value is : true
typeof T is : function
value is : () => {}
typeof T is : object
value is : [object Object]
همانطور که از این خروجی می بینیم، theidentity
تابع در واقع تقریباً با هر نوع که میتوانیم به آن بپردازیم کار میکند.
همچنین می توانیم این تابع را به صورت زیر فراخوانی کنیم:
identity<string>("string");
در اینجا، ما از چیزی استفاده میکنیم که به نام ریختهگری نوع شناخته میشود، یعنی از براکتهای زاویهدار استفاده میشود تا به صراحت مشخص کنیم که این تابع را با چه نوع فراخوانی میکنیم.
اگر به روش قبلی که ما به آن نام زدیم نگاه کنید indentity
تابع، ما به صراحت نوع را با استفاده از این نماد فرم طولانی تنظیم نکردیم، بلکه به سادگی آن را فراخوانی کردیم
تابع با آرگومان، یعنی identity(1)
. در این مثال، TypeScript نوع را استنباط می کند T
عدد بودن
همچنین توجه داشته باشید که اگر به صراحت نوع مورد استفاده را با استفاده از این نماد طولانی تنظیم کنیم، قوانین نوع برای هر نوع استفاده از نوع T اعمال خواهد شد. مثال زیر را در نظر بگیرید:
identity<string>(1);
در اینجا، ما به صراحت مشخص می کنیم که تابع با یک نوع فراخوانی می شود، اما آرگومان واحد ما در واقع از نوع شماره است. این کد خطای زیر را ایجاد می کند:
error TS2345: Argument of type '1' is not assignable to parameter of type 'string'
این خطا به ما می گوید که ما در حال تلاش برای فراخوانی یک تابع عمومی با نوع اشتباه به عنوان آرگومان هستیم، زیرا نوع T به صراحت تنظیم شده است.
اگر به صراحت نوع تابع عمومی را مشخص نکنیم
باید استفاده کند، با حذف مشخص کننده، کامپایلر typescript نوع مورد استفاده را از نوع هر آرگومان استنتاج خواهد کرد.
نوع ژنریک چندگانه
Type Script همچنین به شما امکان می دهد چندین پارامتر نوع برای کلاس ها، رابط ها و توابع تعریف کنید. در اینجا مثالی از استفاده از چند پارامتر نوع عمومی در Type Script آورده شده است:
function printPair<T1, T2>(pair: [T1, T2]): void {
const [first, second] = pair;
console.log(`First: ${first}, Second: ${second}`);
}
printPair<string, number>(["Hello", 42]); // Output: First: Hello, Second: 42
printPair<number, boolean>([3.14, true]); // Output: First: 3.14, Second: true
در اینجا، ما یک تابع داریم printPair
با دو پارامتر نوع عمومی، T1
و T2
. تابع یک جفت آرایه از دو عنصر را می گیرد و مقادیر آنها را در کنسول ثبت می کند.
هنگام تماس با printPair
تابع، انواع پارامترهای نوع عمومی را در براکت های زاویه (<>) مشخص می کنیم. در اولین فراخوانی، یک آرایه از نوع را ارسال می کنیم [string, number]
به تابع، نشان می دهد که عنصر اول یک رشته و عنصر دوم یک عدد است. در فراخوانی دوم، یک آرایه از نوع را ارسال می کنیم [number, boolean]
، مشخص می کند که عنصر اول یک عدد و عنصر دوم یک بولی است.
اجازه دهید مثال دیگری را ببینیم که در آن از چندین پارامتر نوع عمومی در TypeScript استفاده می کنیم:
class Pair<T1, T2> {
private first: T1;
private second: T2;
constructor(first: T1, second: T2) {
this.first = first;
this.second = second;
}
getFirst(): T1 {
return this.first;
}
getSecond(): T2 {
return this.second;
}
setFirst(first: T1): void {
this.first = first;
}
setSecond(second: T2): void {
this.second = second;
}
}
const pair1: Pair<string, number> = new Pair("Hello", 42);
const pair2: Pair<number, string> = new Pair(3.14, "World");
یک کلاس Pair با دو نوع پارامتر تعریف می کنیم، T1
و T2
. کلاس دارای ویژگی های اول و دوم از انواع است T1
و T2
، به ترتیب. سازنده و متدهای کلاس نیز از پارامترهای نوع عمومی استفاده می کنند.
ما می توانیم نمونه هایی از کلاس Pair را با تعیین انواع پارامترهای نوع ایجاد کنیم. در مثال، pair1 یک نوع دارد Pair<string, number>
نشان دهنده یک جفت رشته و یک عدد است، در حالی که جفت ۲ یک نوع دارد Pair<number, string>
نشان دهنده یک جفت عدد و یک رشته است.
محدود کردن نوع T
محدود کردن عمل محدود کردن نوع T به منظور اجازه استفاده از مجموعه خاصی از انواع در کد عمومی ما است. این به اجرای ایمنی نوع کمک می کند و کنترل بیشتری بر انواعی که می توانند با یک نوع یا عملکرد عمومی استفاده شوند، فراهم می کند. در اینجا چند راه برای اعمال محدودیت در TypeScript وجود دارد:
محدودیت های نوع با بسط:
می توانید از کلمه کلیدی extends برای اعمال نوع عمومی استفاده کنید T
باید نوع خاصی را گسترش دهد یا شرایط خاصی را برآورده کند. مثلا:
interface Printable {
print(): void;
}
function printItem<T extends Printable>(item: T): void {
item.print();
}
class Book implements Printable {
print(): void {
console.log("Printing book...");
}
}
printItem(new Book()); // Output: Printing book...
در مثال بالا، printItem
تابع یک پارامتر عمومی را می پذیرد T
توسط رابط قابل چاپ محدود شده است. این به این معنی است که T
باید نوعی باشد که رابط قابل چاپ را پیاده سازی کند. بنابراین، میتوانیم نمونهای از کلاس Book (که Printable را پیادهسازی میکند) به printItem
تابع.
استفاده از T به عنوان نوع برگشتی: شما می توانید از T به عنوان نوع برگشتی یک تابع استفاده کنید و به تماس گیرنده اجازه می دهد تا نوع خاص برگردانده شده را بر اساس ورودی تعیین کند. مثلا:
function identity<T>(value: T): T {
return value;
}
const result = identity("Hello");
console.log(result.toUpperCase()); // Output: HELLO
در مثال بالا، تابع هویت یک پارامتر عمومی T را می گیرد و همان مقدار نوع T را برمی گرداند. هنگام فراخوانی تابع با رشته “Hello”، نوع نتیجه استنباط شده رشته است و می توانیم از روش های خاص رشته مانند استفاده کنیم. toUpperCase()
بر روی آن.
استفاده از تقاطع (&) برای اعمال محدودیت های متعدد:
با استفاده از عملگر نوع تقاطع می توانید چندین محدودیت اعمال کنید (&)
. این تضمین می کند که پارامتر نوع عمومی تمام شرایط مشخص شده را برآورده می کند. در اینجا یک مثال است:
interface Printable {
print(): void;
}
function printItem<T extends Printable & { name: string }>(item: T): void {
console.log(item.name);
item.print();
}
class Book implements Printable {
name: string;
constructor(name: string) {
this.name = name;
}
print(): void {
console.log("Printing book:", this.name);
}
}
const book = new Book("The TypeScript Guide");
printItem(book); // Output: The TypeScript Guide \n Printing book: The TypeScript Guide
در این مورد، printItem
تابع یک نوع T عمومی را انتظار دارد که دو شرط را برآورده کند: رابط قابل چاپ را گسترش دهد و دارای یک ویژگی نام از نوع رشته باشد. کلاس Book این شرایط را برآورده می کند، بنابراین فراخوانی می شود printItem(book)
نام کتاب را با موفقیت چاپ می کند و روش چاپ را فراخوانی می کند.
استفاده از محمولات نوع: محمولات نوع به شما امکان می دهد با استفاده از اظهارات نوع و بررسی های منطقی، محدودیت های سفارشی را مشخص کنید. این تکنیک به ویژه در هنگام برخورد با انواع اتحادیه یا اعتبارسنجی زمان اجرا مفید است. در اینجا یک مثال است:
function isNumber(value: unknown): value is number {
return typeof value === "number";
}
function multiplyByTwo<T>(value: T): T | undefined {
if (isNumber(value)) {
return value * 2;
}
return undefined;
}
console.log(multiplyByTwo(5)); // Output: 10
console.log(multiplyByTwo("Hello")); // Output: undefined
در مثال بالا، isNumber
تابع یک گزاره نوع است که بررسی می کند آیا مقدار یک عدد است یا خیر. را multiplyByTwo
تابع از این نوع محمول برای ضرب مشروط مقدار در دو استفاده می کند اگر عددی باشد. در غیر این صورت، تعریف نشده برمی گردد. این امکان انجام عملیات نوع خاص را در عین حفظ ایمنی نوع فراهم می کند.
استفاده از T برای تعریف خواص یا متدها: می توانید از T برای تعریف ویژگی ها یا متدها در یک کلاس یا رابط استفاده کنید. این به کلاس یا رابط اجازه می دهد تا با انواع مختلف بر اساس استفاده واقعی کار کند. در اینجا یک مثال است:
class Container<T> {
private value: T;
constructor(value: T) {
this.value = value;
}
getValue(): T {
return this.value;
}
}
const container = new Container<string>("Hello");
console.log(container.getValue()); // Output: Hello
در این مثال، کلاس Container دارای یک پارامتر نوع عمومی T است و خاصیت value از نوع T است. ما میتوانیم یک نمونه از Container با یک نوع خاص، در این مورد، رشته ایجاد کنیم و مقدار ذخیره شده را با استفاده ازgetValue
روش.
با اعمال محدودیتها در TypeScript، میتوانید اطمینان حاصل کنید که انواع عمومی به الزامات خاص پایبند هستند، که منجر به کد قویتر و قابل پیشبینی میشود.