تایپ empty object، یعنی {}در تایپ اسکریپت به شکلی که ما انتظار داریم رفتار نمیکند. یعنی به جای نمایش یک آبجکت خالی، هر مقداری را نشان میدهد به جز مقادیر nullو undefined.
دلیل این اتفاق این است که سیستم تایپ در تایپ اسکریپت به شکل structural است، نه nominal. بنابراین همه چیز به جز nullو undefinedیک آبجکت هستند. در نتیجه هر چیزی را میتوانیم به یک آبجکت خالی نسبت دهیم.
اگر میخواهیم یک شی خالی را نشان دهیم، میتوانیم به جای آن از Record<string, never>استفاده کنیم.
تایپ empty object در تایپ اسکریپت واقعاً آنطور که انتظار داریم رفتار نمیکند. یعنی این که نشان دهنده any object نیست. در عوض، هر مقداری که nullیا undefinedنباشد را ارائه میدهد:
const example1: {} = "str";
const example2: {} = 123;
const example3: {} = true;
const example4: {} = {
foo: "whatever",
};
در این مثال، ما example1را به عنوان یک آبجکت خالی ایجاد میکنیم، اما میتوانیم یک رشته را به آن ارسال کنیم. همچنین میتوانیم یک عدد به آن بفرستیم. علاوه بر این می توانیم یک Boolean و یا هر آبجکت دیگری را به آن ارسال نماییم.
تنها چیزهایی که نمیتوانیم به آن ارسال کنیم مقادیر nullیا undefinedاست:
const example5: {} = null;
Type 'null' is not assignable to type '{}'.
const example6: {} = undefined;
Type 'undefined' is not assignable to type '{}'.
اگر از ESLint استفاده میکنیم، حتی ممکن است با خطا زیر مواجه شویم:
Don't use {} as a type. {} actually means "any non-nullish value".
در این مقاله به شکل دقیق بررسی میکنیم که چرا این توصیه خوب است.
این موضوع همچنین با تایپ Objectنیز اتفاق میافتد، که به نظر میرسد فقط یک نام مستعار برای {}میباشد:
const obj1: Object = "str"; const obj2: Object = null; Type 'null' is not assignable to type 'Object'.
بنابراین این دقیقاً به همان شیوه رفتار میکند. در نتیجه ما نیز نباید از این تایپ Objectاستفاده کنیم.
اگر بخواهیم نوعی آبجکت خالی را نشان دهیم، میتوانیم از Record<PropertyKey, never>استفاده کنیم.
type EmptyObj = Record<PropertyKey, never>;
const emptyObj1: EmptyObj = {};
const emptyObj2: EmptyObj = {
foo: "whatever",
Type 'string' is not assignable to type 'never'.
};
const emptyObj3: EmptyObj = "str";
Type 'string' is not assignable to type 'EmptyObj'.
const emptyObj4: EmptyObj = 123;
Type 'number' is not assignable to type 'EmptyObj'.
کاری که این کد کار انجام میدهد این است که میتوانیم یک آبجکت خالی را ارسال کنیم. این کار، ما را از ارسال هر چیزی که property روی آن است باز میدارد. همچنین از ارسال موارد primitive یا null و یا undefined نیز جلوگیری میکند.
این مفهوم تنها زمانی ممکن است مفید باشد که بخواهیم یک محدودیت واقعاً گسترده برای یک تابع داشته باشیم. فرض کنید میخواهیم مطمئن شویم چیزی که به تابع ارسال میکنیم، nullیا undefinedنیست:
const myFunc = (constraint: {}) => {};
myFunc("str");
myFunc(123);
myFunc(true);
اما حتی در این شرایط، نمیتوانیم به هیچ ویژگی در constraintدسترسی داشته باشیم:
const myFunc = (constraint: {}) => {
constraint.foo;
Property 'foo' does not exist on type '{}'.
};
با این خطا مواجه میشویم:
Property 'foo' does not exist on type ''.
این اتفاق میافتد زیرا ما در حال تلاش برای دسترسی به یک ویژگی در یک آبجکت خالی هستیم. بنابراین کار زیاد مفیدی نیست.
تنها جایی که این کار میتواند مفید باشد، یا حداقل جایی که میخواهیم در بخش به آن بپردازیم، به عنوان یک محدودیت در یک تابع generic است.
const myGenericFunc = <T extends {}>(t: T) => {
return t;
};
const result1 = myGenericFunc("str");
const result2 = myGenericFunc(123);
const result3 = myGenericFunc(true);
داخل تایع myGenericFunc، میخواهیم مطمئن شویم که نمیتوانیم nullیا undefinedرا به تابع generic ارسال کنیم. اگر ماوس را روی myGenericFuncنگه داریم، میبینیم که strرا میگیرد و آن را در نتایج return میکند.
اما زمانی که سعی میکنیم به صورت null یا undefined ارسال کنیم، با خطا مواجه میشویم.
const result4 = myGenericFunc(null);
Argument of type 'null' is not assignable to parameter of type '{}'.
const result5 = myGenericFunc(undefined);
Argument of type 'undefined' is not assignable to parameter of type '{}'.
با توجه به مفاهیمی که در این مقاله با آنها آشنا شدیم، ما نباید در هیچ بخشی از تایپ {}استفاده کنیم. زیرا یک قانون linting پیدا کردیم که ما را از استفاده از آن باز میدارد. به همین دلیل است که این تایپ، چیزی که ما فکر میکنیم را نشان نمیدهد، اما گاهی اوقات برای محدود کردن genericها مفید میباشد.