انواع پیشرفته در تایپ اسکریپت – از صفر تا صد

۱۷۶ بازدید
آخرین به‌روزرسانی: ۰۳ مهر ۱۴۰۲
زمان مطالعه: ۵ دقیقه
انواع پیشرفته در تایپ اسکریپت – از صفر تا صد

تایپ اسکریپت ظرفیت‌های نوعی پیشرفته‌ای دارد که موجب می‌شود نوشتن کد با نوع‌بندی دینامیک به کار آسانی تبدیل شود. همچنین استفاده از کد جاوا اسکریپت موجود را تسهیل می‌کند، زیرا امکان حفظ ظرفیت دینامیک جاوا اسکریپت را در عین استفاده از ظرفیت‌های بررسی نوع تایپ اسکریپت فراهم می‌سازد. تعداد زیادی از انواع پیشرفته در تایپ اسکریپت وجود دارند که شامل انواع intersection، انواع union، گاردهای نوع، انواع تهی پذیر (nullable)، اسامی مستعار نوع (type aliases) و موارد دیگر می‌شود. در این مقاله به بررسی نوع this و ایجاد انواع دینامیک با امضای اندیس و انواع نگاشت یافته (Mapped) می‌پردازیم.

نوع This

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

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

1class StringAdder {
2  value: string = '';
3  getValue(): string {
4    return this.value;
5  }
6  addFoo(): this {
7    this.value += 'foo';
8    return this;
9  }
10  addBar(): this {
11    this.value += 'bar';
12    return this;
13  }
14  addGreeting(name: string): this {
15    this.value += `Hi ${name}`;
16    return this;
17  }
18}
19const stringAdder: StringAdder = new StringAdder();
20const str = stringAdder
21  .addFoo()
22  .addBar()
23  .addGreeting('Jane')
24  .getValue();
25console.log(str);

در کد فوق، متدهای addFoo ،addBar و addGreeting همگی وهله‌هایی از کلاس StringAdder بازگشت می‌دهند که به ما امکان می‌دهد تا فراخوانی‌های متد بیشتری از وهله را پس از وهله‌سازی به آن نسبت دهیم. این زنجیره‌سازی در نتیجه نوع بازگشتی this که در هر متد داریم امکان یافته است.

انواع اندیس

برای این که کامپایلر تایپ اسکریپت بتواند کد با نام‌های مشخصه دینامیک را بررسی کند، می‌توانیم از انواع اندیس استفاده کنیم. بدین ترتیب می‌توان از ترکیب کلیدواژه extends keyof برای نشان دادن این که نوع دارای نام‌های مشخصه نوع دیگر است بهره گرفت. برای نمونه می‌تواند کدی مانند زیر نوشت:

1function choose<U, K extends keyof U>(o: U, propNames: K[]): U[K][] {
2  return propNames.map(n => o[n]);
3}

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

1function choose<U, K extends keyof U>(o: U, propNames: K[]): U[K][] {
2  return propNames.map(n => o[n]);
3}
4const obj = {
5  a: 1,
6  b: 2,
7  c: 3
8}
9choose(obj, ['a', 'b'])

سپس مقادیر زیر به دست می‌آید:

[1, 2]

اگر نتایج تابع choose را لاگ کنیم، در صورت ارسال یک نام مشخصه به آرایه بعد از تابع choose که در شیء obj موجود نیست، با خطایی از سوی کامپایلر تایپ اسکریپت مواجه خواهیم شد. بنابراین اگر چیزی مانند کد زیر بنویسیم:

1function choose<U, K extends keyof U>(o: U, propNames: K[]): U[K][] {
2  return propNames.map(n => o[n]);
3}
4const obj = {
5  a: 1,
6  b: 2,
7  c: 3
8}
9const arr = choose(obj, ['d']);

با خطای زیر مواجه می‌شویم:

Type 'string' is not assignable to type '"a" | "b" | "c"'. (2322)

در مثال فوق، keyof U همان نوع «لفظ رشته‌ای» (string literal) به صورت “a” | “b” | “c” است زیرا نوع ژنریک مربوط به نشانگر نوع U را که نوع واقعی استنباط شده از شیء است را ارسال کرده‌ایم و به آرگومان نخست می‌فرستیم. بخش K extends keyof U به این معنی است که آرگومان دوم باید از برخی یا همه نام‌های کلید هر چیزی که در آرگومان نخست ارسال شده باشد و با نوع U ژنریک نمایش داده می‌شود. در این صورت نوع بازگشتی را به صورت آرایه‌ای از مقادیر تعریف می‌کنیم که با تعریف حلقه‌ای روی شیء و ارسال آرگومان نخست به دست می‌آوریم. بدین ترتیب نوع []U[K] را داریم. نوع []U[K] به نام عملگر «دسترسی اندیس» (Index Access) نیز نامیده می‌شود.

انواع اندیس و امضاهای اندیس

امضای اندیس پارامتری است که باید در اینترفیس تایپ اسکریپت از نوع رشته یا عدد باشد. می‌توان از آن برای نمایش مشخصه‌های یک شیء دینامیک بهره جست. برای نمونه می‌توانیم از آن مانند کد زیر استفاده کنیم:

1interface DynamicObject<T> {
2  [key: string]: T;
3}
4let obj: DynamicObject<number> = {
5  foo: 1,
6  bar: 2
7};
8let key: keyof DynamicObject<number> = 'foo';
9let value: DynamicObject<number>['foo'] = obj[key];

در کد فوق یک اینترفیس <DynamicObject<T تعریف کرده‌ایم که یک key را که رشته است فرا می‌خواند. البته می‌تواند یک عدد نیز باشد. نوع اعضای دینامیک با T نمایش می‌یابد که نشانگر نوع ژنریک است. این بدان معنی است که می‌توانیم هر نوع داده‌ای را به آن ارسال کنیم.

سپس شیء obj را تعریف کرده‌ایم که از نوع <DyanmicObject<number است. این کار موجب می‌شود که از اینترفیس DynamicObject که پیش‌تر ساختیم بهره بگیریم. سپس متغیر key را تعریف می‌کنیم که نوع keyof DynamicObject<number دارد. معنی آن این است که می‌تواند یک رشته یا عدد باشد. در کل متغیر key باید یکی از نام‌های مشخصه را به عنوان مقدار داشته باشد. در ادامه متغیر value را تعریف می‌کنیم که باید مقدار یک شیء از نوع DynamicObject داشته باشد.

این بدان معنی است که نمی‌توانیم هیچ چیزی به جز یک رشته یا عدد به متغیر key انتساب دهیم. بنابراین اگر کدی مانند زیر بنویسیم:

1let key: keyof DynamicObject<number> = false;

با پیام خطایی مانند زیر از سوی کامپایلر تایپ اسکریپت مواجه می‌شویم:

Type 'false' is not assignable to type 'string | number'. (2322)

انواع نگاشت‌یافته

امکان ایجاد یک نوع جدید با نگاشت اعضای یک نوع موجود به نوع جدید وجود دارد. این نوع جدید به نام «نوع نگاشت‌یافته» (Mapped Type) خوانده می‌شود. امکان ساخت انواع نگاشت‌یافته مانند مواردی که در کد زیر می‌بینید وجود دارد:

1interface Person {
2  name: string;
3  age: number;
4}
5type ReadOnly<T> = {
6  readonly [P in keyof T]: T[P];
7}
8type PartialType<T> = {
9  [P in keyof T]?: T[P];
10}
11type ReadOnlyPerson = ReadOnly<Person>;
12type PartialPerson = PartialType<Person>;
13let readOnlyPerson: ReadOnlyPerson = {
14  name: 'Jane',
15  age: 20
16}
17readOnlyPerson.name = 'Joe';
18readOnlyPerson.age = 20;

در کد فوق یک نام مستعار نوع به صورت ReadOnly ساختیم تا بتوانیم اعضای نوع موجود را به یک نوع جدید نگاشت دهیم. این کار با تعیین هر عضو به نوع readonly امکان یافته است. این یک نوع جدید برای خود محسوب نمی‌شود چون باید یک نوع به نشانگر نوع ژنریک T ارسال کنیم. در ادامه یک اسم مستعار برای نوعی که با ارسال نوع Person به ترتیب به اسم مستعار ReadOnly و اسم مستعار Partial ساخته‌ایم، ایجاد می‌کنیم.

سپس یک شیء ReadOnlyPerson تعریف کرده و مشخصه‌های name و age را تعیین می‌کنیم. در ادامه تلاش می‌کنیم این مقدار را مجدداً تنظیم کنیم که با خطای زیر مواجه می‌شویم.

Cannot assign to 'name' because it is a read-only property. (2540)
Cannot assign to 'age' because it is a read-only property. (2540)

معنی خطای فوق این است که مشخصه readonly از اسم مستعار نوع ReadOnly الزام شده است. به طور مشابه می‌توانیم همین کار را با اسم مستعار نوع PartialType انجام دهیم. ما نوع PartialPerson ر با نگاشت اعضای نوع Person به نوع PartialPerson با نوع PartialPerson تعریف کرده‌ایم. سپس می‌توانیم یک شیء PartialPerson مانند زیر تعریف کنیم:

1let partialPerson: PartialPerson = {};

چنان که می‌بینید، می‌توانیم مشخصه‌های شیء PartialPerson را بنا به نیاز نادیده بگیریم.

امکان افزودن اعضای جدید به اسم مستعار نوع نگاشت‌یافته با ایجاد یک نوع intersection از روی آن فراهم می‌شود. از آنجا که از کلیدواژه type برای تعریف انواع نگاشت‌یافته استفاده می‌کنیم، در واقع انواع واقعی هستند. در عمل آن‌ها اسم مستعار نوع محسوب می‌شوند. این بدان معنی است که نمی‌توانیم اعضا را مستقیماً درون آن‌ها قرار دهیم، هر چند شبیه اینترفیس به نظر می‌رسند.

برای افزودن عضو می‌توانیم کدی مانند زیر بنویسیم:

1interface Person {
2  name: string;
3  age: number;
4}
5type ReadOnly<T> = {
6  readonly [P in keyof T]: T[P];
7}
8type ReadOnlyEmployee = ReadOnly<Person> & {
9  employeeCode: string;
10};
11let readOnlyPerson: ReadOnlyEmployee = {
12  name: 'Jane',
13  age: 20,
14  employeeCode: '123'
15}

<Readonly<T و <Partial<T در کتابخانه استاندارد تایپ اسکریپت موجود هستند. اعضای نوعی که به درون نوع ژنریک ارسال می‌شوند را به اعضای read-only نگاشت می‌کند. کلیدواژه Partial به ما امکان می‌دهد که اعضای یک نوع را به اعضای nullable نگاشت کنیم.

سخن پایانی

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

می‌توان از امضای اندیس برای نمایش مشخصه‌های یک شیء دینامیک بهره گرفت. برای تبدیل اعضای یک نوع و افزودن برخی خصوصیت‌ها به آن می‌توانیم اعضای یک نوع موجود را به نوع جدیدی نگاشت کنیم و خصوصیت‌های مورد نظر را به همراه انواع نگاشت‌یافته به اینترفیس بیفزاییم.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۰ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
gitconnected
نظر شما چیست؟

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