Everyday Types
استفاده از انواع داده های پرکاربرد در تایپ اسکریپت
The Primitives
سه تایپ string، number و boolean از همه متداول تر اند و در TypeScript و JavaScript یکسان هستند.
Array
برای تعریف آرایهای از هر نوع مقدار استفاده میشود. انواع روشهای تعریف تایپ از نوع آرایه:
Any
هرگز پیشنهاد نمی شود از این نوع استفاده کنید زیرا هر نوع داده ای را مجاز می کند و عملاً حضور TypeScript را بی تأثیر میکند. این نوع زمانی استفاده می شود که TypeScript نوع داده ای را تشخیص نمی دهد یا نوع داده مدنظر نامشخص است.
اگر نوع متغیر را مشخص نکنید و تایپ اسکریپت هم نتواند نوعش را تخمین (infer) بزند،
کامپایلر بهطور پیشفرض آن را any در نظر میگیرد. برای جلوگیری از این اتفاق، گزینه زیر را در فایل tsconfig.json خود تنظیم کنید:
Type Annotations on Variables
زمانی که از var، const یا let برای تعریف متغیر استفاده می کنیم، می توانیم یک توضیح نوع به آن اضافه کنیم تا نوع متغیر به صورت کاملاً صریح مشخص شود.
زمانی که متغیر را مقداردهی میکنیم، نوع آن به صورت کاملاً صریح برای TypeScript مشخص است. بنابراین در این مواقع نیازی به نوشتن توضیح نوع نیست و انجام این کار باعث طولانیتر شدن کد میشود.
Function
تایپ اسکریپت این امکان را میدهد که نوع پارامتر ها و خروجی های توابع را مشخص کنیم.
Parameter Type Annotations
نوع هر پارامتر را میتوانیم بعد از نام آن مشخص کنیم.
در این تابع، ورودی name از نوع string تعریف شده. بنابراین تایپ اسکریپت انتظار دارد هنگام استفاده از تابع،آرگومان اول را از نوع string پاس دهیم.
Return Type Annotations
در تایپ اسکریپ میتوانید نوع مقداری را که یک تابع برمیگرداند (return میکند)، بعد از پارامتر ها مشخص کنید.
در اغلب موارد نیازی به تعریف نوع بازگشتی به صورت دستی نیست. چون خود تایپ اسکریپت به طور نوع خروجی را از روی مقدار return شده تشخیص(infer) میدهد.
چرا بعضی از برنامهنویسان نوع بازگشتی را مینویسند؟
- برای مستندسازی بهتر کد
- برای جلوگیری از تغییر ناخواستهی نوع بازگشتی در آینده
- یا صرفاً بهعنوان سبک شخصی یا قراردادی در پروژه.
Functions Which Return Promises
مشخص کردن نوع توابعی که خروجی آنها به صورت یک Promise است:
Anonymous Function
TypeScript می تواند نوع ورودی های تابع ناشناس را بسته به موقعیت مورد استفاده تشخیص دهد.
در مثال زیر، TypeScript نوع ورودی name را به دلیل اینکه names آرایهای از رشتهها است و متد forEach هم قرار است که تک تک این رشتهها را به تابع ناشناس ارسال کند، تشخیص میدهد:
Object Types
برای تعریف نوع یک Object کافی است ویژگیها و نوع آنها را فهرست کنید.
در مثال زیر، نوع ورودی pt که یک Object است، در همان زمان تعریف نوشته شده است:
برای جدا کردن پراپرتی(property) میتوان از ,(کاما) یا ;(سمیکالن) استفاده کرد(قرار دادن جدا کننده اختیاری است).
یکی دیگر از روش های تعیین نوع برای Object، استفاده از interface است.
Optional Property
برای ایجاد یک پراپرتی اختیاری، باید بعد از نام پراپرتی و قبل از :، علامت ? را قرار دهیم:
برای استفاده ایمن تر از پارامتر های اختیاری باید آنها را بررسی کنیم که undefined نباشند.
Union Type
تایپ اسکریپت این امکان را میدهد که با استفاده از عملگرها type جدیدی را از ترکیب type های موجود ساخت. یکی از ساده ترین و پرکاربرد ترین روش ها برای ترکیب type ها، union type(نوع ترکیبی) است.
Defining a Union Type
یک Union Type از دو یا چند type دیگر ساخته میشود و نشان میدهد که یک مقدار میتواند یکی از آن type ها باشد.
وقتی یک متغیر از نوع union تعریف میشود، فقط میتوان عملیاتهایی روی آن انجام داد که برای هر یک از اعضای union قابل انجام باشد.
در مثال زیر ورودی id میتواند رشتهای یا عددی باشد. از متدهایی میتوان استفاده کرد که هم برای نوع number و هم string در دسترس باشند.
میتوان قبل از اولین عضو، | گذاشت تا type خواناتر شود:
Working with Union
وقتی متغیری از نوع ترکیبی(union) دارید (مثل string | number)، مقدار ورودی میتواند هر یک از اعضای union باشد. اما تایپ اسکریپت فقط اجازهی استفاده از عملیاتهایی را میدهد که برای تمام اعضای آن union معتبر باشند.
در مثال زیر تایپ اسکریپت اجازه استفاده از متد toUpperCase را نمیدهد چون ممکن است ورودی عدد باشد نه یک رشته:
برای حل این مشکل، باید نوع را محدود(narrow) کنید؛ یعنی به تایپ اسکریپت نشان دهید در هر بخش از کد، دقیقاً کدام نوع مدنظر شماست.
در مثال زیر، تایپ اسکریپت بهطور خودکار متوجه میشود در شاخهی if مقدار از نوع string و در else از نوع number است.
اگر تمام اعضای یک union دارای ویژگی مشترک باشند، میتوانید مستقیماً از آن ویژگی استفاده کنید بدون اینکه type ها را محدود(narrow) کنید.
Type Aliases
تا اینجا از type های object و union به صورت مستقیم استفاده کردیم. اما معمولاً نیاز داریم یک نوع را چندین بار استفاده کنیم و بخواهیم آن را با یک نام مشخص فراخوانی کنیم. Type Alias دقیقاً برای همین کار است؛ یعنی یک نام برای هر نوع (Type) تعریف میکند.
ساختار آن به شکل زیر است:
شما میتوانید از Type Alias نه فقط برای object ها، بلکه برای نامگذاری هر نوعی استفاده کنید. برای مثال، میتوان به یک Union Type نام داد:
نکته: Type Alias نوع جدیدی ایجاد نمیکند؛ فقط یک نام جدید برای یک نوع(type) موجود است. کد زیر شاید در نگاه اول غیرمجاز به نظر برسد، اما از نظر TypeScript کاملاً معتبر است، چون هر دو نوع در واقع همان string هستند:
Interfaces
Interface راه دیگری برای نامگذاری یک نوع object است
همانند مثالی که با type دیدیم، این کد دقیقاً به همان شکل کار میکند و یک تایپ از نوع object تعریف میکند.
تایپاسکریپت فقط به ساختار مقداری که به تابع printCoord ارسال شده اهمیت میدهد؛ یعنی فقط بررسی میکند که آیا ویژگیهای مورد انتظار (x و y) وجود دارند یا خیر.
به همین دلیل به تایپاسکریپت یک سیستم نوعدهی ساختاری (Structural Type System) میگویند.
Differences Between Type Aliases and Interfaces
type و interface بسیار شبیه یکدیگر هستند و در بسیاری از موارد میتوان هر کدام را به جای دیگری استفاده کرد. تقریباً تمام قابلیت های interface در type نیز وجود دارند. مهم ترین تفاوت آنها این است که type را نمیتوان دوباره باز و ویژگی جدیدی به آن اضافه کرد در حالی که interface همیشه قابل گسترش است.
گسترش پذیری:
- type
- interface
افزودن ویژگی جدید:
- type: بعد از ایجاد قابل تغییر نیست
- interface
در فصلهای بعدی بیشتر با این مفاهیم آشنا خواهید شد، بنابراین اگر در حال حاضر همه آنها را کاملاً درک نمیکنید، نگران نباشید.
- type alias ها نمیتوانند در Declaration Merging شرکت کنند، اما interface ها میتوانند.
- Interfaceها فقط برای تعریف شکل اشیاء قابل استفاده هستند و نمیتوان از آنها برای نامگذاری مجدد نوع های Primitive استفاده کرد.
- نام interface ها در پیامهای خطا همیشه به همان شکل اصلی خود نمایش داده میشوند، البته فقط زمانی که از طریق نامشان استفاده شده باشند.
- استفاده از interface ها با
extendsمعمولا میتواند برای کامپایلر عملکرد بهتری داشته باشد نسبت به type alias ها با (&)intersections.
در بیشتر مواقع میتوانید بر اساس سلیقه شخصی خود بین type و interface انتخاب کنید و تایپاسکریپت به شما خواهد گفت که به نوع دیگری از تعریف نیاز دارید یا نه. تا زمانی که به قابلیتهای خاص type نیاز پیدا نکردهاید، از interface استفاده کنید.
Type Assertions
گاهی اوقات اطلاعاتی درباره نوع یک مقدار دارید که TypeScript آن را نمیداند.
به عنوان مثال، اگر در حال استفاده از document.getElementById باشید، TypeScript فقط میداند که این متد نوعی HTMLElement برمیگرداند، اما ممکن است شما بدانید که صفحه مدنظرتان دارای یک HTMLCanvasElement با یک ID مشخص است.
در این شرایط، میتوانید از Type Assertion برای مشخص کردن یک نوع دقیقتر استفاده کنید:
مانند type annotation، type assertion ها توسط کامپایلر حذف میشوند و بر رفتار runtime کد شما تأثیری نخواهند داشت. همچنین میتوانید از سینتکس angle-bracket (به جز زمانی که کد در یک فایل .tsx باشد) استفاده کنید که معادل است:
یادآوری: از آنجا که Type Assertion ها در زمان کامپایل حذف میشوند، بنابراین بررسی و صحت سنجی نخواهند شد. اگر Type Assertion نادرست باشد، هیچ Exception(خطا) یا مقدار null تولید نخواهد شد.
TypeScript فقط به Type Assertion هایی اجازه میدهد که یک نوع را به نسخهای دقیقتر یا عمومیتر تبدیل کنند. این قانون از Coercion(اجبار) های "غیرممکن" مانند زیر جلوگیری میکند:
گاهی اوقات این قانون ممکن است بیش از حد محتاطانه باشد و Coercion(اجبار) های پیچیدهتر که ممکن است معتبر باشند را رد کند. اگر چنین اتفاقی افتاد، میتوانید از دو Type Assertion پشت سر هم استفاده کنید؛ ابتدا به any یا unknown، و سپس به نوع مورد نظر تبدیل کنید:
Literal Types
علاوه بر type های عمومی string و number، میتوان در موقعیتهای type به رشتهها و اعداد مشخص اشاره کرد.
یکی از روشهای درک این موضوع، توجه به این است که JavaScript راههای مختلفی برای تعریف یک متغیر ارائه میدهد. مقدار متغیر های تعریف شده با کلمات کلیدی var و let قابل تغییر است، اما const چنین امکانی را نمیدهد. تایپ اسکریپت از همین قانون برای ساخت literal type ها استفاده میکند.
literal type ها تنهایی چندان هم کاربردی نیستند:
داشتن متغیری که فقط بتواند یک مقدار داشته باشد، چندان کاربردی نیست! اما با ترکیب literalها در Union، میتوانید مفهوم بسیار کاربردیتری را بیان کنید؛ برای مثال، تابعی که فقط مجموعه مشخصی از مقادیر شناختهشده را میپذیرد:
Literal Typeهای عددی نیز به همین شکل عمل میکنند:
البته، میتوانید اینها را با typeهای غیر literal نیز ترکیب کنید:
یک نوع دیگر از Literal Type نیز وجود دارد: boolean literal. فقط 2 تا تایپ برای literal type از نوع boolean وجود دارد که عبارتاند از false و true. خود type boolean در واقع فقط یک نام مستعار (alias) برای Union true | false است.
Literal Inference
وقتی یک متغیر را با یک object مقداردهی اولیه میکنید، TypeScript فرض میکند که propertyهای آن object ممکن است بعداً تغییر کنند. برای مثال، اگر کدی مانند زیر بنویسید:
TypeScript فرض نمیکند که انتساب مقدار 1 به propertyای که قبلاً مقدار 0 داشته است، یک خطا باشد. به بیان دیگر، obj.counter باید از نوع number باشد، نه 0، چون نوع داده برای تعیین رفتار خواندن و نوشتن استفاده میشود.
همین موضوع برای stringها نیز صدق میکند:
در مثال بالا، type مربوط به req.method بهصورت string تشخیص داده میشود، نه "GET". دلیل این موضوع این است که ممکن است بین ایجاد req و فراخوانی handleRequest کدی اجرا شود که مقدار req.method را به string دیگری مانند "GUESS" تغییر دهد. بنابراین، TypeScript این کد را دارای خطا در نظر میگیرد.
دو روش برای رفع این مشکل وجود دارد.
- میتوانید با اضافه کردن یک type assertion در یکی از دو محل زیر، نتیجه تشخیص type را تغییر دهید:
Change 1 به این معناست کهreq.methodهمیشه دارای literal type "GET" است در نتیجه انتساب مقادیر دیگر به این property در ادامه امکانپذیر نخواهد بود. Change 2 به این معناست کهreq.methodدر ورودی تابع دارای مقدار "GET" است - میتوانید از as const استفاده کنید تا کل object به literal typeها تبدیل شود:
پسوند as const در سیستم type مشابه const عمل میکند و تضمین میکند که بهجای اختصاص یافتن typeهای کلیتر مانند string یا number، به تمام propertyها literal type اختصاص داده شود.
