انواع ژنریک تایپ اسکریپت به عنوان پارامتر — راهنمای مقدماتی
اگر تاکنون در پی پیادهسازی اصول DRY و ایجاد دینامیک عالی و کد با قابلیت استفاده مجدد بوده باشید، احتمالاً با مفهوم انواع ژنریک تایپ اسکریپت مواجه شدهاید. با این که این مفهوم در دنیای برنامهنویسی، مفهوم جدیدی محسوب نمیشود، اما اگر از بدو پیدایش جاوا اسکریپت تاکنون فقط با این زبان کار کرده باشید، احتمالاً این مفهوم برای شما نو خواهد بود. تایپ اسکریپت یک زبان سوپرست خارقالعاده برای جاوا اسکریپت محسوب میشود که به ما امکان استفاده از مفهوم ژنریکها برای نوشتن کد زیبا، دینامیک و با قابلیت استفاده مجدد را میدهد.
ما گاهبهگاه با موقعیتهایی مواجه میشویم که مدلهای داده متعددی برای بازنمایی اپلیکیشن نیاز داریم. برای نمونه فرض کنید مشغول ساختن اپلیکیشنی هستید که شرکتهای مختلف، محصولاتی که میفروشند و خریداران آن محصولات را ردگیری میکند. در چنین موقعیتی باید برخی مدلهای تایپ اسکریپت برای نمایش این کلاسهای متنوع از دادهها ایجاد کنیم:
1export class Company {
2 companyName: string;
3 location?: string;
4 type?: string;
5 age?: string;
6
7 static tableName: string = 'Companies';
8 static displayProp: string = 'companyName';
9}
10
11export class Product {
12 productName: string;
13 type?: string;
14 price?: number;
15 dateCreated?: date;
16
17 companyId?: string;
18
19 static tableName: string = 'Products';
20 static displayProp: string = 'productName';
21}
22
23export class Buyer {
24 buyerName: string;
25 location?: string;
26 productsPurchased?: Product[];
27
28 static tableName: string = 'Buyers';
29 static displayProp: string = 'buyerName';
30}
ما با توجه به منظورمان از این مثال، چند props استاتیک نیز به هر یک از این کلاسها اضافه کردیم. ضمناً رشتههای displayProp خاص را که قرار است برای تعیین مشخصههای مدل برای نمایش استفاده شوند، نیز تعریف کردیم. به علاوه یک مشخصه tableName داریم که میتوان آن را به عنوان نام پایگاه دادهای که مدل به آن اشاره دارد تصور کرد.
اکنون میخواهیم لیستی از همه شرکتها، محصولات و خریداران آن محصولات داشته باشیم. اینک میتوانیم یک کامپوننت لیست خاص برای هر شرکت، محصول و خریدار بسازیم، اما چنان که حدس میزنید بخش بزرگی از کد برای هر نوع مدل تکراری خواهد بود. بدین ترتیب کد ما قابلیت نگهداری پایینی خواهد داشت، زیرا با افزایش پیچیدگی اپلیکیشن، باید نوعهای داده بیشتری را در یک لیست نمایش دهیم.
ایجاد یک کامپوننت لیست دینامیک
به جای ایجاد یک لیست برای تکتک انواع داده، میتوانیم یک کامپوننت ژنریک ایجاد کنیم که نوع دادهها برایش مهم نیست.
به این منظور از انگولار استفاده میکنیم، اما مفاهیم مورد استفاده در فریمورکهای دیگر و حتی خود جاوا اسکریپت محض نیز یکسان هستند.
1@Component({
2 selector: 'app-generic-list',
3 template: `
4 <ul>
5 <li *ngFor="let listItem of listItems">
6 <h4>{{listItem[modelType.displayProp}}</h4>
7 </li>
8 </ul>
9 `,
10 styles: [`
11 ul {
12 list-style: none;
13 }
14 `]
15})
16export class GenericListComponent<T> implements OnInit {
17 @Input() listItems: T = [];
18 @Input() modelType: { new(...args: any[]): T; };
19
20 constructor() {}
21
22 ngOnInit() { }
23}
در اینجا متوجه میشویم که باید یک کلاس ژنریک با افزودن پارامتر <T> به نام کلاس بسازیم. این کار به ما امکان میدهد که از نوع ژنریک روی مشخصههای متنوع کلاس استفاده کنیم با این حال، نامهای متفاوتی برای مشخصه names در مدلهای مختلف مانند productName ،companyName ،buyerName و غیره داریم. بنابراین اینک سؤال این است که چگونه میتوانیم به این مشخصهها در عنصر <h4> خود اشاره کنیم؟
استفاده از نوع ژنریک به مثابه یک پارامتر
در ادامه یک مشخصه خاص روی کامپوننت میبینید که به منظور ارجاع به یک نوع ژنریک استفاده شده است و میتوانیم از آن به صورت دینامیک استفاده کنیم، طوری که گویی همان شیء واقعی است:
const modelType: { new(...args: any[]): T; };
این ساختارِ غریبی است، اما در واقع اعلام میکنیم که modelType باید یک شیء ژنریک باشد و کلیدواژه new میتواند هر تعداد مشخصه که لازم باشد بگیرد. بدین ترتیب میتوانیم به صورت دینامیک یک شیء جدید از نوع ژنریک به صورت درجا در کامپوننت ژنریک خود بسازیم:
const newGenericObj = new modelType({});
در این مثال، صرفاً باید به مشخصه استاتیک مشخصه display در مدل در تگ <h4> دسترسی پیدا کنیم، از این رو حتی نیازی به ایجاد یک شیء ژنریک جدید نداریم و صرفاً یک ارجاع به نوع ژنریک درون کامپوننت لیست ژنریک ارسال میکنیم:
1import { Product, Company, Buyer } from './AppModels.ts';
2
3@Component({
4 selector: 'app-generic-list-example',
5 template: `
6 <main>
7 <div class="col-4">
8 <app-generic-list [listItems]="products" [modelType]="productRef"></app-generic-list>
9 </div>
10 <div class="col-4">
11 <app-generic-list [listItems]="companies" [modelType]="companyRef"></app-generic-list>
12 </div>
13 <div class="col-4">
14 <app-generic-list [listItems]="buyers" [modelType]="buyerRef"></app-generic-list>
15 </div>
16 </main>
17 `,
18 styles: [`
19 main {
20 display: flex;
21 justify-content: space-between;
22 }
23
24 col-4 {
25 width: 33%;
26 }
27 `]
28})
29export class GenericListComponent<T> implements OnInit {
30 productRef: Product;
31 companyRef: Company;
32 buyerRef: Buyer;
33
34 products: Product[] = [];
35 companies: Company[] = [];
36 buyers: Buyer[] = [];
37
38 constructor() {}
39
40 ngOnInit() { }
41}
برای پایداری بیشتر کد، میتوانیم یک وهله نیز ایجاد کنیم که تنظیمات پارامتری این نوع ژنریک را تعریف میکند و همچنین مشخصههای استاتیکی را که مدل ما انتظار دارد دریافت کند، نیز تعریف کنیم:
1// Generic Model Parameter
2export interface IGenericModel<T> {
3 new(...args: any[]): T;
4 tableName: string;
5 displayProp: string;
6}
سپس میتوانیم مثال قبلی را طوری بازسازی کنیم که مشخصه modelType اینک به صورت نوع <IGenericModel<T باشد.
البته این تنها راه هم نیست. شاید فکر کنید ایجاد آن مشخصههای ref در مثال قبلی، بیش از حد طولانی شده است. روش دیگری نیز برای تعریف مشخصه modelType وجود دارد که به صورت زیر است:
//(data model is the class name of your model) modelType: <T>({}) => T;
در کد فوق اعلام میکنیم که modelType تابعی خواهند بود که نوع ژنریک را بازگشت میدهد. در واقع چیزی که اعلام میکنیم دقیقاً معادل مثال قبلی است. بدین ترتیب میتوانیم صرفاً ارجاع نوع را به کامپوننت generic-list ارسال کنیم. بنابراین به جای نوشتن کدی مانند زیر:
[modelType]="productRef"
میتوانیم از کد زیر استفاده کنیم:
[modelType]="Product"
سخن پایانی
ژنریکها گاهی اوقات کمی سردرگمکننده هستند، اما زمانی که با ساختار آنها آشنا شوید و مفهوم کلی را درک کنید، میتوانید این مفهوم را در حوزههای وسیعی برای جدا ساختن کامپوننتهای UI از دادهها مورد استفاده قرار دهید. خوشبختانه مفهوم استفاده از انواع به عنوان پارامتر به ما کمک میکند که کد دینامیکتر و ژنریکتری بنویسیم.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای جاوا اسکریپت
- مجموعه آموزشهای برنامهنویسی
- راهنمای جامع تایپ اسکریپت (Typescript) — از صفر تا صد
- درک انواع در تایپ اسکریپت — به زبان ساده
- مفهوم تابع در تایپ اسکریپت — به زبان ساده
==