رفتارهای محدودسازی نوع متفاوت بین توابع معمولی و عبارات تابع در تایپاسکریپت
function foo(item: string | null) {
if (!item) return null
return () => {
function regular() {
return item
}
const arrow = () => item
console.log(regular().toLowerCase()) // Error: Object is possibly 'null'.
console.log(arrow().toLowerCase()) // Pass
}
}
زمین بازی اینجا
در تابع foo من دو تابع تو در تو نوشتم. یکی تابع معمولی است و دیگری تابع فلشی. در حالی که انتظار دارم هر دو مقدار برگشتی به نوع string محدود شوند، واقعیت این است که regular() نوع string | null دریافت کرده است.
در ابتدا فکر کردم ممکن است به این دلیل باشد که پارامتر تابع item قابل تغییر است. (در این مسئله ذکر شده) پس من آن را به صورت دستی به const تبدیل کردم، اما هنوز همان مشکل وجود دارد:
function foo(item: string | null) {
const itemCopy = item // Make sure TypeScript know it is immutable
if (!itemCopy) return null
return () => {
function regular() {
return itemCopy
}
const arrow = () => itemCopy
console.log(regular().toLowerCase()) // Error: Object is possibly 'null'.
console.log(arrow().toLowerCase()) // Pass
}
}
زمین بازی اینجا
همچنین ذکر کنید که به نظر نمیرسد با توابع فلشی مرتبط باشد. تغییر تابع فلشی به یک عبارت تابع معمولی نیز همان رفتار را نشان خواهد داد.
function foo(item: string | null) {
if (!item) return null
return () => {
function regular() {
return item
}
const exp = function () { // Now the only difference is one is function expression while the other is not.
return item
}
console.log(regular().toLowerCase()) // Error: Object is possibly 'null'.
console.log(exp().toLowerCase()) // Pass
}
}
زمین بازی اینجا
پس چرا چنین است؟ آیا این رفتار طبق طراحی مورد نظر است؟
پاسخ شماره ۱
به نظر میرسد که این همانطور که قصد شده کار میکند، طبق microsoft/TypeScript#32300. از نظر بنیادی، تعاریف/بیانیههای تابع رفتاری متفاوت از عبارات تابع دارند به این صورت که تعریفهای تابع بالا برده میشوند. تعریفهای تابع بالا برده شده میتوانند قبل از اینکه تعریفهایشان در کد ظاهر شوند، فراخوانی شوند، در حالی که عبارات تابع که بالا برده نمیشوند چنین قابلیتی ندارند:
function foo(item: string | null) {
if (!item) return null
return () => {
regular(); // okay
exp(); // error
function regular() {
return item
}
const exp = function () {
return item
}
}
}
این بدان معنی است که برای تحلیل جریان کنترل مفاهیم مختلفی وجود دارد. به نظر میرسد برای توابع بالابرده شده (hoisted)، تایپاسکریپت به سادگی تسلیم میشود و به صورت بدبینانه فرض میکند که هیچیک از متغیرهای بستهبندیشده محدود نشدهاند، در حالی که برای عبارات تابع (function expressions) به صورت خوشبینانه فرض میکند که هر محدودیتی از قبل ادامه دارد. مشخص نیست که آیا هیچکدام از این رفتارها "درست" است یا نه، اما مطمئناً طبق طراحی عمل میکند.
مسئله کلی محدود شدن در مواجهه با متغیرهای بسته به طور عمیق در microsoft/TypeScript#9998 بحث شده است. تایپاسکریپت بسیاری از فرضیات سادهسازیکننده را ارائه میدهد که در بسیاری از موقعیتها به اندازه کافی خوب عمل میکنند اما در موارد لبهای نتایج منفی و مثبت کاذب ناخوشایندی نشان میدهند. معمولاً هنگامی که فردی یک ناسازگاری پیدا میکند، مشخص میشود که این یک باگ نیست بلکه یک انتخاب طراحی با معایبی است.
لینک زمین بازی به کد
