نکات و ترفندهای تایپ اسکریپت — راهنمای کاربردی

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

در این مقاله برخی نکات و ترفندهای مختلف مرتبط با زبان برنامه‌نویسی TypeScript را با هم مرور می‌کنیم. در ادامه 10 مورد از نکات و ترفندهای تایپ اسکریپت را ملاحظه خواهید کرد که مطالعه آن برای هر توسعه‌دهنده‌ای با هر سطح از مهارت مناسب است.

1. TypeScript و DOM

زمانی که شروع به استفاده از TypeScript بکنید خیلی زود متوجه می‌شوید که در زمینه کار با محیط مرورگر کاملاً گزینه مناسبی محسوب می‌شود.

فرض کنید می‌خواهیم یک عنصر <input> را روی یک صفحه پیدا کنیم:

1const textEl = document.querySelector('inpot');
2console.log(textEl.value); 
3// ? Property 'value' does not exist on type 'Element'.

بنابراین به ما نشان می‌دهد که خطایی وجود دارد، زیرا در متد querySelector یک غلط املایی داریم و به جای input به دنبال inpot می‌گردیم. تایپ‌اسکریپت از طریق فایل lib.dom.d.ts که بخشی از کتابخانه تایپ‌اسکریپت است و اساساً همه چیز (شیء، تابع‌ها، رویدادها) را توصیف می‌کند این وضعیت را مدیریت می‌کند. بخشی از تعریف، اینترفیسی است که درون نوع‌بندی متد thequerySelector استفاده می‌شود و رشته‌هایی (مانند "div", "table" یا "input") را به انواع عناصر متناظر HTML نگاشت می‌کند:

1interface HTMLElementTagNameMap {
2    "a": HTMLAnchorElement;
3    "abbr": HTMLElement;
4    "address": HTMLElement;
5    "applet": HTMLAppletElement;
6    "area": HTMLAreaElement;
7    "article": HTMLElement;
8    /* ... */
9    "input": HTMLInputElement;
10    /* ... */
11}

این راه‌حل چندان جالب نیست زیرا تنها برای سلکتورهای عناصر ابتدایی کار می‌کند، اما به هر حال بهتر از هیچ است. مثل دیگر از این رفتار هوشمندانه تایپ‌اسکریپت زمانی است که با رویدادهای مرورگر کار می‌کنیم:

1textEl.addEventListener('click', e => {
2    console.log(e.clientX);
3});

در مثال فوق clientX. یک مشخصه موجود یا هر نوع رویداد مفروض نیست بلکه صرفاً روی MouseEvent موجود است. تایپ‌اسکریپت نوع رویداد را بر مبنای این مقدار رشته‌ای "click" که نخستین آرگومان در متد addEventListener است درک می‌کند.

2. ژنریک‌های مورد انتظار

اگر به جای یک سلکتور عنصر از هر چیز دیگری استفاده کنیم:

1document.querySelector('input.action')

در این صورت HTMLELementTagNameMap نمی‌تواند مفید باشد و تایپ‌اسکریپت صرفاً یک نوع Element نسبتاً مقدماتی بازگشت می‌دهد.

همانند querySelector در غالب موارد کاربرد این گونه است که یک تابع می‌تواند ساختارهای متفاوتی را بازگشت دهد و در این شرایط تعیین این که کدام یک بازگشت خواهد یافت برای تایپ‌اسکریپت ناممکن است. در چنین حالتی با تقریب بالایی می‌توان انتظار داشت که تابع مذکور نیز یک ژنریک است و می‌توان نوع بازگشتی را در یک ساختار ژنریک آسان ارائه کرد:

1textEl = document.querySelector<HTMLInputElement>('input.action');
2console.log(textEl.value);
3// ? 'value' is available because we've instructed TS
4// about the type the 'querySelector' function works with.

اگر با انگولار کار کرده باشید، در این صورت مثال دیگر می‌تواند کلاس ElementRef باشد:

ترفندهای TypeScript

3. آیا واقعاً یافته‌ایم؟

می‌دانیم که متد (...)document.querySelector عملاً همواره یک شیء بازگشت نمی‌دهد. یک عنصر که با این سلکتور مطابقت پیدا کند می‌تواند یک صفحه نباشد و به جای شیء، یک تابع مقدار null بازگشت دهد. بنابراین دسترسی یافتن به آن شیء، مثلاً مشخصه value. ممکن است همیشه آن گونه که توضیح دادیم پیش نرود.

به صورت پیش‌فرض بررسی‌کننده نوع، مقادیر null و undefined را به هر نوعی قابل انتساب می داند. این وضعیت را می‌توان امن‌تر ساخت و این رفتار را با افزودن بررسی‌های سختگیرانه null در فایل tsconfig.json محدودتر کرد:

1{
2    "compilerOptions": {
3        "strictNullChecks": true
4    }
5}

تایپ‌اسکریپت، با استفاده از تنظیمات فوق، در صورتی که تلاش کنید به مشخصه روی یک شیء که احتمال دارد null باشد، دسترسی یابید اعتراضی نخواهد کرد و در مورد وجود آن شیء از طریق قرار دادن آن بخش درون شرط زیر اطمینان پیدا می‌کنید:

1if (textEl) {...}

کاربردهای رایج دیگر به جز querySelector به صورت متد Array.find است که نتیجه آن احتمالاً undefined است. در واقع ما همواره آن چه را به دنبالش هستیم نمی‌یابیم.

4. اعلام مقدار به تایپ‌اسکریپت

در بخش قبلی بررسی‌های سختگیرانه null را تعریف کردیم و بدین ترتیب تایپ‌اسکریپت در مورد مقادیر مختلف با احتیاط بیشتری برخورد می‌کند. از سوی دیگر برخی اوقات با اطلاع کلی صرفاً می‌دانیم که یک مقدار قرار است تعیین شود. در چنین کاربردهای احتمالی می‌توان از عملگر «عبارت پست فیکس» (post-fix expression) استفاده کرد.

1const textEl = document.querySelector('input');
2console.log(textEl!.value); 
3// ? with "!" we assure TypeScript
4// that 'textEl' is not null/undefined

5. در هنگام مهاجرت به تایپ‌اسکریپت

در اغلب موارد زمانی که یک کدبیس قدیمی دارید که می‌خواهید به تایپ‌اسکریپت تبدیل کنید، یکی از بزرگ‌ترین دردسرها این است که کاری کنید id شما به قواعد TSLint بچسبد. به این منظور می‌توان همه این فایل‌ها را با افزودن کد زیر به نخستین خط هر کدام ویرایش کرد:

1// tslint:disable

بدین ترتیب TSLint عملاً آن‌ها را بررسی نمی‌کند. در این صورت تنها هنگامی که یک توسعه‌دهنده روی یک فایل قدیمی کار کند شروع به حذف این کامنت و اصلاح خطاهای صرفاً روی آن فایل می‌کند. از این رو ما دیگر یک انقلاب نداریم، بلکه با یک تکامل روبرو هستیم و کدبیس به تدریج اما با امنیت بهبود می‌یابد.

همانند افزودن انواع واقعی به کد قدیمی جاوا اسکریپت می‌توانید عملاً در اغلب موارد این کار را انجام ندهید. استنباط نوع تایپ‌اسکریپت این وظیفه را بر عهده می‌گیرد. در صورتی که کدی واقعاً مرتب داشته باشید یعنی نوع‌های مختلفی از مقادیر را به یک متغیر انتساب داده باشید، مشکلی نخواهید داشت. اگر بازسازی کردن این کد آسان نیست می‌توانید از any استفاده کنید:

1let mything = 2;
2mything = 'hi';
3// ? Type '"hi"' is not assignable to type 'number'
4mything = 'hi' as any;
5// ? if you say "any", TypeScript says ¯\_(ツ)_/¯

اما همیشه از این راه‌حل به عنوان آخرین چاره استفاده کنید چون استفاده از any در تایپ‌اسکریپت چندان پسندیده نیست.

6. محدودیت‌های بیشتر نوع

گاهی اوقات تایپ‌اسکریپت نمی‌تواند نوع را استنباط کند. رایج‌ترین کاربرد یک پارامتر تابع است:

1function fn(param) {
2    console.log(param);
3}

تایپ‌اسکریپت به صورت درونی نیاز به انتساب برخی انواع به param دارد و از این رو از any استفاده می‌کند. از آنجا که می‌خواهیم استفاده از any را به کمترین مقدار ممکن برسانیم معمولاً توصیه می‌شود که این رفتار را با تنظیمات tsconfig.json دیگری محدود بکنیم:

1{
2    "compilerOptions": {
3        "noImplicitAny": true
4    }
5}

متأسفانه ما نمی‌توانیم نوعی کمربند ایمنی قرار دهیم چون آن کار به نوع‌بندی صریح روی نوع بازگشتی تابع نیاز دارد. بنابراین اگر به جای function fn(param): string نوع یعنی (function fn(param را فراموش کنیم، تایپ‌اسکریپت توجهی به نوع بازگشتی نخواهد داشت یا حتی اگر هر چیزی را از تابع بازگشت دهیم متوجه نخواهد شد. به بیان دقیق‌تر نوع بازگشتی را از هر چه که بازگشت می‌یابد یا نمی‌یابد استنباط می‌کند.

خوشبختانه TSLint در این موارد به کمک ما می‌آید. با استفاده از قاعده typedef می توان انواع بازگشتی مورد نیاز را تولید کرد:

1{
2    "rules": {
3        "typedef": [
4            true,
5            "call-signature"
6        ]
7    }
8}

این ایده بهتری به نظر می‌رسد.

7. محافظان نوع

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

1type BookId = number | string;
2function returnFormatterId(id: BookId) {
3    return id.toUpperCase();
4    // ? 'toUpperCase' does not exist on type 'number'.
5}
6function returnFormatterId(id: BookId) {
7    if (typeof id === 'string') {
8        // we've made sure it's a string:
9        return id.toUpperCase(); // so it's ?
10    }
11    // ? TS also understands that it
12    // has to be a number here:
13    return id.toFixed(2)
14}

8. نکته‌ای دیگر در مورد ژنریک‌ها

فرض کنید این نوع از سازه کاملاً عمومی را داریم:

1interface Bookmark {
2    id: string;
3}
4class BookmarksService {
5    items: Bookmark[] = [];
6}

شما می‌خواهید از آن در اپلیکیشن‌های مختلف برای نمونه جهت ذخیره‌سازی کتاب‌ها یا فیلم‌ها استفاده کنید. در چنین اپلیکیشن‌هایی باید کاری مانند زیر انجام دهید:

1interface Movie {
2    id: string;
3    name: string;
4}
5class SearchPageComponent {
6    movie: Movie;
7    constructor(private bs: BookmarksService) {}
8    getFirstMovie() {
9        // ? types are not assignable
10        this.movie = this.bs.items[0];
11        // ? so you have to manually assert type:
12        this.movie = this.bs.items[0] as Movie;
13    }
14    getSecondMovie() {
15        this.movie = this.bs.items[1] as Movie;
16    }
17}

این نوع از تأیید نوع ممکن است در آن کلاس چندین بار مورد نیاز باشد. کاری که می‌توان به جای این کار کرد آن است که یک کلاس BookmarksService به صورت ژنریک تعریف کنیم:

1class BookmarksService<T> {
2    items: T[] = [];
3}

اما اینک می‌بینیم که بیش از حد کلی شده است و باید تائید کنیم که انواعی که این کلاس با آن‌ها کار می‌کند با اینترفیس Bookmark مطابقت دارد، یعنی دارای مشخصه id: string است. بنابراین اعلان بهبودیافته به صورت زیر است:

1class BookmarksService<T extends Bookmark> {
2    items: T[] = [];
3}

اینک در SearchPageComponent نوع را تنها یک بار تعیین می‌کنیم:

1class SearchPageComponent {
2    movie: Movie;
3    constructor(private bs: BookmarksService<Movie>) {}
4    getFirstMovie() {
5        this.movie = this.bs.items[0]; // ?
6    }
7    getSecondMovie() {
8        this.movie = this.bs.items[1]; // ?
9    }
10}

یک بهبود دیگر نیز برای این کلاس ژنریک وجود دارد که ممکن است مفید باشد. اگر از آن در جاهای دیگر در آن ظرفیت عمومی استفاده کنید و نخواهید <BookmarksService<Bookmark را در چنین موقعیت‌هایی بنویسید می‌توانید نوع پیش‌فرض را در اعلان ژنریک ارائه کنید:

1class BookmarksService<T extends Bookmark = Bookmark> {
2    items: T[] = [];
3}
4const bs = new BookmarksService();
5// I don't have to provide the type for the generic now
6// - in that case 'bs' will be of that default type 'Bookmark'

9. نوع‌بندی پارامترهای مسیر

این نکته اختصاص به برنامه نویسان انگولار دارد. برای ردگیری پارامترهای مسیر و داده‌های resolve شده یک اینترفیس برای آن‌ها بسازید. سپس نوع‌ها را برای params و data در کامپوننت تأیید کنید تا دیگر نیازی به ردگیری دوباره آن‌ها نداشته باشید.

ترفندهای TypeScript

ترفندهای TypeScript

10. ساخت اینترفیس‌هایی از پاسخ‌های API

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

چند گزینه به این منظور وجود دارند:

  • http://www.json2ts.com
  • http://www.jsontots.com

اما سرور اغلب این سرویس‌ها عموماً از کار می‌افتند. از بین این موارد سرویس زیر عملکرد بهتری دارد:

  • https://app.quicktype.io

همچنین سرویس فوق یک افزونه مناسب برای ویژوال استودیو کد دارد.

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

==

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

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