Píldoras TypeScript: تایپ باریک کردن con “as const”

در بسیاری از مواقع هنگام تخصیص یک مقدار سازگار با نوع مورد انتظار به یک متغیر، با خطایی مانند “رشته قابل انتساب به… هر چه باشد” مواجه شده اید. این زمانی اتفاق میافتد که TypeScript یک نوع گستردهتر از حد انتظار را استنباط میکند برای یک متغیر، مانند زمانی که یک رشته را به متغیری که باید از نوع تحت اللفظی باشد اختصاص می دهیم.
برای درک بهتر، اجازه دهید با یک مثال به آن نگاه کنیم.
ما یک نوع داریم Severity
به عنوان یک اتحاد رشته ای از مقادیر: “کم”، “متوسط” و “بالا” تعریف می شود.
type Severity = "low" | "medium" | "high"
می توانستیم بگوییم Severity
یک زیرگروه از string
که از بین تمام رشته های ممکن، فقط آن سه را پشتیبانی می کند.
از طرف دیگر ما یک شی داریم messages
که در آن کلیدها a Severity
و مقادیر رشته ها هستند.
const messages: RecordSeverity, string> = {
low: "😕 not good",
medium: "😖 ugly",
high: "😱 AAAAHHH!!!",
}
ما می خواهیم به مقادیر آن دسترسی داشته باشیم messages
و برای این یک متغیر تعریف می کنیم severity
با مقدار “کم”.
let severity = "low"
console.log(messages[severity])
// ^? Error: Element implicitly has an 'any' type because
// expression of type 'string' can't be used to index type
// 'Record'.
اگرچه مقداری که به متغیر نسبت داده ایم severity
با نوع آن سازگار است Severity
، TypeScript خطایی را به ما نشان می دهد که به ما می گوید که کلیدها messages
باید از نوع باشد Severity
و نه از نوع string
.
برای درک اینکه چرا این اتفاق می افتد، ما باید بفهمیم که TypeScript چگونه انواع متغیرهایی را استنباط میکند که ما به طور صریح به آنها نوع اختصاص نمیدهیم..
اگر در ویرایشگر خود یا در تایپ اسکریپت Playground روی شناور نگه دارید severity
خواهیم دید که TypeScript آن را به صورت استنباط می کند string
.
این به این دلیل است که ما اعلام کرده ایم severity
استفاده كردن let
و بنابراین می توانیم مقداری را که با آن سازگار نیست دوباره به آن اختصاص دهیم Severity
هر زمان که.
در این موارد، TypeScript چیزی را اعمال می کند که به عنوان ” شناخته می شودگسترده شدن“و وسیع ترین نوع را به متغیر اختصاص می دهد که با مقداری که در ابتدا به آن اختصاص داده ایم سازگار است.
مراقب باشید، من می گویم “در ابتدا” زیرا TypeScript از ما انتظار دارد که مقدار یک متغیر را در زمان اجرا، اما نه نوع آن (از string
آ number
، و غیره.). اگر قصد ما این است، باید به صراحت آن را مشخص کنیم.
اگر متغیر را با تعریف کنیم، همین اتفاق نمی افتد const
.
const severity = "low"
console.log(messages[severity]) // OK
از آنجایی که استفاده از const
اجازه تخصیص مجدد متغیر را نمی دهد، TypeScript نوع تحت اللفظی “low” را استنباط می کند و خطا ناپدید می شود.
بنابراین، به عنوان یک قاعده کلی (به استثنای برخی موارد) TypeScript وسیع ترین نوع را برای متغیرهای نوع اولیه اعلام شده با استنباط می کند let
و نوع تحت اللفظی برای متغیرهای اعلام شده با const
.
برای متغیرهای نوع ابتدایی بسیار زیاد است، اما چه اتفاقی برای اشیا می افتد؟
بیایید آن را با یک مثال دیگر ببینیم.
ما یک رابط داریم Issue
با این خواص:
interface Issue {
id: string
severity: Severity
}
و یک تابع printIssue
که یک شی از نوع را دریافت می کند Issue
و کاری با آن انجام دهد، که برای ما مهم نیست.
declare function printIssue(issue: Issue): void
اگر بریم به printIssue
یک شی با ساختاری سازگار با Issue
، TypeScript خطایی را به ما نشان می دهد که نشان می دهد انواع severity
با هم سازگار نیستند.
const issue = {
id: '123',
severity: 'low',
}
printIssue(issue)
// ^? Error: Argument of type '{ id: string; severity:
// string; }' is not assignable to parameter of type 'Issue'.
// Types of property 'severity' are incompatible.
در مورد اشیا صرف نظر از اینکه آنها را با اعلام کنیم let
o const
، TypeScript نوع ویژگی های خود را به گونه ای استنباط می کند که گویی با آنها اعلام شده اند let
.
اگرچه ما نمی توانیم مقدار جدیدی را به متغیری از نوع شی که با آن اعلام شده است اختصاص دهیم const
، بله می توانیم ویژگی های آن را تغییر دهیم.
issue.severity = 'other'
اگر مدتی است که با TypeScript کار می کنیم، می دانیم که می توانیم این مشکل را از چند راه حل کنیم و یکی از آنها استفاده از as const
در اموال ناسازگار
const issue = {
id: '123',
severity: 'low' as const,
}
با استفاده از as const
ما به TypeScript می گوییم که کوچکترین نوع ممکن را استنتاج کند (باریک شدن) برای ملک severity
به جای نوع گسترده تر string
. در این صورت نوع تحت اللفظی «کم» استنباط می شود.
یکی دیگر از راه های حل این مشکل، برای اهداف آموزشی، استفاده است Object.freeze
در مورد شی
const issue = Object.freeze({
id: '123',
severity: 'low',
})
با توجه به Object.freeze
یک شی فقط خواندنی را برمی گرداند، TypeScript نوع تحت اللفظی را برای ویژگی های آن استنباط می کند.
Object.freeze
یک ویژگی دارد که در برخی موارد می تواند یک ایراد باشد و آن این است که تغییر ناپذیری آن این است که “کم عمق“، یعنی فقط روی خصوصیات شی تاثیر می گذارد، اما روی ویژگی های تودرتو آن تاثیر نمی گذارد، که هنوز هم می توان آنها را تغییر داد.
اگر ما علاقه مند به علامت گذاری یک شی به عنوان هستیم readonly
در عمق، ما می توانیم اعمال کنیم as const
به کل شی
const issue = {
id: '123',
severity: 'low',
another: {
prop: 'value'
}
} as const
و این برای استنتاج نوع و as const
به عنوان یک “تکنیک”باریک شدن“، اما ما راه های دیگری برای جلوگیری از استنباط TypeScript انواع گسترده تر از آنچه ما انتظار داریم، داریم، مانند تعیین صریح نوع متغیرها، با استفاده از satisfies
یا استفاده از “بررسی اموال مازاد“، اما ما آن را برای یک قرص دیگر رها می کنیم.
اگر تا اینجا پیش رفتید، ممنونم! و اگر شما هم آن را مفید دیدید یا به سادگی آن را دوست داشتید، با اشتراک گذاری آن به من کمک کنید تا به افراد بیشتری دسترسی پیدا کنم 😊