Narrowing
محدود کردن نوع متغیر به یک نوع خاص تر بر اساس بررسی های منطقی
در تایپ اسکریپت، Narrowing به فرآیندی گفته میشود که در آن نوع (type) یک مقدار از یک حالت کلیتر (مثل string | number) به حالتی خاصتر (مثل فقط string یا فقط number) محدود میشود، تا بتوانیم با اطمینان از ویژگیهای آن نوع استفاده کنیم.
typeof type guards
تمام نوع های زیر توسط عملگر typeof پشتیبانی می شوند. این عملگر نوع یک مقدار را به صورت رشتهای بر میگرداند.
- string
- number
- bigint
- boolean
- symbol
- undefined
- object
- function
این نکته را مدنظر داشته باشید که نوع مقدار null وArray در جاوا اسکریپت object است
عملگر هایی مانند == ، === ، != ، !== و دستور switch کمک می کنند که نوع یک مقدار در یک بلوک خاص مشخص شود.
Truthiness narrowing
در جاوااسکریپت هر مقدار در موقعیت هایی مانند if، &&، ||، ! که مقدار boolean لازم هست، به true یا false تبدیل میشود. به این ویژگی truthiness میگویند.
در جاوااسکریپت، ساختارهای شرطی مثل if ابتدا مقدار شرط را به boolean تبدیل میکنند (coercion) و سپس مسیر اجرایی را بر اساس true یا false انتخاب میکنند.
مقادیر زیر در شرط بالا به false تبدیل میشوند و بقیهٔ مقادیر به true:
- 0
- NaN
- "" (رشتهٔ خالی)
- 0n (صفر از نوع bigint)
- null
- undefined
میتوانید با تابع Boolean یا با دو عملگر نقیض !! این تبدیل را صراحتاً انجام دهید.(پیشنهاد میشود روش دوم را استفاده کنید تا تایپ اسکریپت نوع آن را دقیقا تشخیص دهد)
استفاده از این رفتار بسیار رایج است، بهویژه برای کنترل مقادیر null یا undefined. به عنوان مثال تابع printAll را با این روش مینویسیم:
با بررسی truthiness بودن strs، از خطای TypeError: null is not iterable جلوگیری کردیم. اما کنترل truthiness بودن مقادیر میتواند خطا هم داشته باشد. به مثال زیر دقت کنید:
تمام کد ها را در if truthiness قرار دادیم. مشکل اینجاست که یک رشته خالی چون truthy نیست، چاپ نمی شود.
Equality narrowing
TypeScript از دستور switch و عملگرهای برابری (===, !==, ==, !=) نیز برای محدود کردن type استفاده میکند. برای مثال:
وقتی بررسی کردیم که x و y برابرند، TypeScript فهمید نوع آنها نیز باید یکسان باشد. از آنجا که string تنها نوع مشترک بین x و y است، پس در شاخهٔ اول قطعاً هر دو string خواهند بود.
در بخش قبلی که دربارهٔ «narrowing با truthiness» صحبت کردیم، تابع printAll را نوشتیم که بهخاطر استفاده از if (strs) رشتهٔ خالی ("") را اشتباهاً کنار میگذاشت.برای رفع این مشکل، بهجای تکیه به truthiness، میتوانیم بهطور خاص فقط null را حذف کنیم. با این کار مقدار null را از لیست type های strs حذف میکنیم و فقط string | string[] باقی میماند.
The in operator narrowing
این عملگر وجود یک پراپرتی را در type مدنظر بررسی می کند، و true یا false را بر می گرداند.
در مثال زیر بررسی شده است که آیا پراپرتی swim در object animal وجود دارد یا خیر. با این کار مشخص می شود که داخل بلوک if ، object animal از نوع Fish است و به پس بنابراین میتوان گفت که عملیات narrowing در این قسمت انجام شده است.
instanceof narrowing
در جاوا اسکریپت عملگر instanceof بررسی میکند که مقدار مورد نظر، نمونه ای از نوع مشخص شده هست یا نه.
در مثال زیر ، جاوااسکریپت، x instanceof Foo بررسی میکند که آیا x نمونهای از Foo است یا نه. در TypeScript، این بررسی به TypeScript کمک میکند تا نوع x را درون بلاک if محدودتر کند.
Assignments
تایپ اسکریپت همیشه هنگام مقدار دهی های بعدی متغیر ها ، بررسی میکند که مقدار اختصاص یافته به آن، با نوع اصلی متغیر هنگام تعریفش مطابقت دارد یا نه.
Control flow analysis
تایپ اسکریپت فقط به یک شرط نگاه نمیکند بلکه کل جریان منطقی اجرا را نگاه میکند (مانند return, if-else, switch-case, break, continue...) تا در هر موقعیتی دقیق ترین نوع مقدار یا متغیر را تشخیص دهد.
Using type predicate
Type Predicate در تایپ اسکریپت عبارتی است که به کامپایلر میگوید اگر یک تابع مقدار true برگرداند، نوع یک متغیر را بهصورت دقیقتر (narrowed) در نظر بگیرد. یعنی بعد از return true, نوع بازگشتی تابع ، نوعی است که در type predicate مشخص شده است.
به مثال زیر توجه کنید:
Assertion Functions
در تایپاسکریپت، توابع تایید (Assertion Functions) توابع خاصی هستند که اگر شرط خاصی برقرار نباشد، بلافاصله با پرتاب یک AssertionError اجرای برنامه را متوقف میکنند. این کار در Node.js با تابع آمادهٔ assert شناخته میشود:
در این مثال اگر someValue با مقدار 42 برابر نباشد، خطای AssertionError رخ میدهد تا از ادامه اجرای اشتباه جلوگیری شود.
هدف تایپاسکریپت این است که کمترین تغییر را در کدهای جاوااسکریپت موجود ایجاد کند. از اینرو در TypeScript 3.7 مفهومی جدیدی به نام امضاء های تایید (assertion signature) معرفی شد.
روش اول: شبیه assert در Node.js
این روش تضمین میکند که تا پایان محدوده فعلی، آن شرط حتماً درست است.
نحوه نگارش این روش:
عبارت asserts condition میگوید اگر تابع بدون خطا برگردد، پس شرط condition قطعاً درست است. در نتیجه در ادامهی کد، کامپایلر میتواند روی درست بودن آن حساب کند.
اینجا برخلاف مثال قبلی، تایپاسکریپت خطای املای متد را میگیرد، چون پس از assert میداند که str یک string است.
روش دوم: بررسی نوع متغیر
این روش تعیین میکند که یک متغیر، type مشخصی دارد.
asserts val is string تضمین میکند که بعد از فراخوانی این تابع، متغیری که به آن پاس داده ایم از نوع string خواهد بود.
این روش خیلی شبیه به پیشبین های نوع (type predicates) است.
The never type
در فرآیند narrowing (محدودسازی نوع)، ممکن است یک نوع union (اتحاد چند نوع) آنقدر محدود شود که دیگر هیچ گزینهای باقی نماند. در چنین حالتی، تایپاسکریپت از نوع ویژهای به نام never استفاده میکند.
برای اطمینان از پوشش تمام حالات یک union، از نوع never استفاده میکنیم:
در این مثال همه حالت های union پوشش داده شده است. اگر روزی یک status جدید اضافه شود که ما آن را پوشش نداده باشیم، خطای کامپایل ظاهر میشود؛ چون که status با یک مقدار string به متغیر _exhaustiveCheck از نوع never پاس داده شده است. با وجود این خطا متوجه میشویم که باید مقدار جدید status را در if ها پوشش دهیم.
Discriminated unions
در تایپاسکریپت، Discriminated Unions به شما اجازه میدهند تا با استفاده از یک ویژگی مشترک (Discriminant)، اعضای مختلف یک Union Type را از هم تفکیک کنید. این کار به کامپایلر تایپاسکریپت کمک میکند تا بداند در هر بخش از کد، با کدام نوع از اعضای union سر و کار دارد و از بروز خطاهای احتمالی جلوگیری میکند.
فرض کنید میخواهیم شکل هایی مثل دایره و مربع را مدل سازی کنیم:
- دایره شعاع(radius) دارد.
- مربع طول ضلع(sideLength) دارد.
برای تشخیص نوع شکل از یک فیلد به نام kind استفاده میکنیم.
رویکرد اول(اشتباه) این است که تمام اَشکال را به صورت union در kind قرار داده و ویژگی های آنها را به صورت اختیاری(optional) در یک interface تعریف کنیم:
مشکل اصلی زمانی است که میخواهیم تابع محاسبه مساحت را بسازیم:
با وجود اینکه نوع شکل بررسی شده است که حتما circle باشد، اما چون تایپ اسکریپت نمیداند radius حتما در shape وجود دارد؛ بنابراین خطا میدهد.
رویکرد صحیح این است که شکل های مختلف را با مشخصات مختلف از یکدیگر جدا کنیم.
حالا تمام اَشکال را در یک union قرار میدهیم. با این کار تایپ اسکریپت به خوبی متوجه میشود که هر شکل شامل چه ویژگی هایی است:
در این مرحله تایپاسکریپت میتواند از ویژگی kind به عنوان تفکیک کننده(discriminant) استفاده کند.
ویژگی kind یک نوع literal (circle یا square) است.
وقتی که ما kind را بررسی میکنیم، تایپاسکریپت به صورت هوشمند نوع شکل را محدود(narrowing) میکند و بعد از آن تمام ویژگی های متعلق به شکل را میشناسد.
Exhaustiveness checking
در تایپ اسکریپت ، بررسی کامل (exhaustiveness checking) به ما کمک می کند که مطمئن شویم در یک ساختار کنترلی مانند switch همه حالت ها برای یک نوع union کنترل شود.
بروز خطا در قسمت default به ما اطلاع میدهد که مقدار جدید shape.kind را در switch پوشش دهیم.
