دلیل خطای Object literal may only specify known properties تایپ اسکریپت بررسی وجود Property اضافی است تا مانع از پذیرش آن شود، در این مطلب به دو حالت مختلف تفسیر Interface در تایپ اسکریپت اشاره میکنیم و سپس راه حلهای مختلف حل این مشکل را معرفی میکنیم.
Excess Property Checks یا بررسی پراپرتی اضافی
مثال زیر را به عنوان Interface و متد زیر را در نظر بگیرید.
interface Point {
x: number;
y?: number;
}
function computeDistance(point: Point) { /*...*/ }
دو حالت مختلف کلی را برای شیوهی تفسیر این interface در تایپ اسکریپت تصور کرد:
۱ـ تفسیر بسته یا Closed interpretation: در این حالت هر object که دقیقا شامل پراپرتیهای اینترفیس هستن قابل قبولاند که در این بین پراپرتی که الزامی نیست مثل y در مثال بالا میتواند حضور داشته باشد یا خیر اما وجود x الزامی است و درصورت وجود یک پراپرتی اضافی (Excess Property) مثل z با خطا روبرو خواهیم شد.
computeDistance({ x: 1, y: 2, z: 3 });
// Object literal may only specify known properties, and 'z' does not exist in type 'Point'.(2345)
۲ـ تفسیر باز یا Opened interpretation: در این حالت کافیست یک شیئ دارای یک زیر مجموعهای از اینترفیس مورد نظر باشد برای مثال آبجکتی شامل پراپرتی x و z در این حالت قابل قبول هست چون دارای پراپرتیهای الزامی اینترفیس Point است و از طرفی دیگر داشتن پراپرتی اضافی z مانند مثال قبل در این حالت باعت ایجاد خطا نمیشود و پذیرفتهاست.
const obj = { x: 1, z: 3 };
computeDistance(obj); // OK
Excess Property Checks یا بررسی پراپرتی اضافی چه کمکی میکند؟
زمانی که ورودی یک متد را با یک متغیر که بصورتهای مختلف در برنامه ایجاد میشود مقداردهی میکنیم Opened interpretation رخ میدهد و بررسی وجود پراپرتی اضافی بررسی نمیشود ولی در حالتی که خودمان بصورت یک Object literal به عنوان ورودی تابع در نظر میگیریم Closed interpretation رخ میدهد و اگر در زمان نوشتن پراپرتیهای آبجکت خطای تایپی رخ دهد که در این حالت احتمال بیشتری دارد تایپ اسکریپت آن را به عنوان یک پراپرتی اضافی در نظر گرفته و هشدار میدهد.
مثال زیر را در نظر بگیرید:
interface Person {
first: string;
middle?: string;
last: string;
}
function computeFullName(person: Person) { /*...*/ }
فرض کنید بصورت زیر و به روش Object literal میخواستیم ورودی تابع computeFullName را مقداردهی کنیم ولی بجای middle به اشتباه mdidle تایپ کردیم.
computeFullName({first: 'Jane', mdidle: 'Cecily', last: 'Doe'});
// @ts-ignore: Argument of type '{ first: string; mdidle: string; last: string; }' is not assignable to parameter of type 'Person'.
// Object literal may only specify known properties, but 'mdidle' does not exist in type 'Person'. Did you mean to write 'middle'?
همین طور که مشاهده میکنید به لطف وجود Excess Property Checks در تایپ اسکریپت این هشدار واضح را مبنی بر اینکه احتمالا اشتباهاً بجای `middle` عبارت `mdidle` را تایپ کردیم نمایش داده میشود.
روشهای جلوگیری از بررسی وجود پراپرتیهای اضافی
۱ـ استفاده از یک متغیر واسط برای مقداردهی به متد یک تابع
const obj = { x: 1, y: 2, z: 3 };
computeDistance1(obj);
۲ـ استفاده از type assertion بصورت زیر
computeDistance1({ x: 1, y: 2, z: 3 } as Point); // OK
۳ـ بازنویسی متد computeDistance1 و استفاده از یک type parameter
function computeDistance2<P extends Point>(point: P) { /*...*/ }
computeDistance2({ x: 1, y: 2, z: 3 }); // OK
۴ـ بازنویسی اینترفیس Point با اضافه کردن یک Index signature بصورت زیر برای اینکه پراپرتیهای اضافی را بپذیرد
interface Person {
first: string;
middle?: string;
last: string;
// Index signature
[propName: string]: any;
}
در ادامه یک مثالی را بررسی میکنیم که Excess Property Checks تایپ اسکریپت مشکل ایجاد میکند.
در مثال زیر میخواهیم Incrementor را پیادهسازی کنیم ولی تایپ اسکریپت اجازهی اضافه کردن یک پراپرتی جدید به عنوان counter را نمیدهد.
interface Incrementor {
inc(): void
}
function createIncrementor(start = 0): Incrementor {
return {
// @ts-ignore: Type '{ counter: number; inc(): void; }' is not assignable to type 'Incrementor'.
// Object literal may only specify known properties, and 'counter' does not exist in type 'Incrementor'.(2322)
counter: start,
inc() {
// @ts-ignore: Property 'counter' does not exist on type 'Incrementor'.(2339)
this.counter++;
},
};
}
حتی زمانیکه از type assertion استفاده میکنیم همچنان مشکل باقی است.
function createIncrementor2(start = 0): Incrementor {
return {
counter: start,
inc() {
// @ts-ignore: Property 'counter' does not exist on type 'Incrementor'.(2339)
this.counter++;
},
} as Incrementor;
}
برای حل این مشکل میتوان از دو روش زیر استفاده کرد:
۱ـ استفاده از متغیر واسط
function createIncrementor3(start = 0): Incrementor {
const incrementor = {
counter: start,
inc() {
this.counter++;
},
};
return incrementor;
}
۲ـ در صورت امکان اضافه کردن Index signature به اینترفیس Incrementor
interface Incrementor {
inc(): void;
[propName: string]: any;
}