وابستگی نوع بین آرگومانهای تابع با بارگذاری بیش از حد تابع در تایپاسکریپت
من در حال ایجاد یک تابع کارخانهای هستم که یک آرگومان نوع میگیرد تا نوع شیء را که باید ایجاد شود مشخص کند، به علاوه یک آرگومان پارامتر دارد که شامل پارامترهایی است که شیء را توصیف میکنند و این پارامترها بسته به نوع شیء متفاوت هستند. یک مثال ساده شده به این شکل خواهد بود:
enum EntityType {
TABLE,
BUCKET
}
type TableParams = { tableName: string }
type BucketParams = { bucketName: string }
function createEntity (type: EntityType.TABLE, params: TableParams): void
function createEntity (type: EntityType.BUCKET, params: BucketParams): void
function createEntity (type: EntityType, params: TableParams | BucketParams): void {
switch(type) {
case EntityType.TABLE:
console.log('table name:', params.tableName)
break
case EntityType.BUCKET:
console.log('bucket name:', params.bucketName)
break
}
}
بارگذاری بیش از حد توابع اطمینان میدهد که کاربران فقط میتوانند تابع را با نوع درست پارامترها فراخوانی کنند، بسته به نوع موجودیت، مثلاً.
createEntity(EntityType.TABLE, { tableName: 'foo' })
createEntity(EntityType.BUCKET, { bucketName: 'bar' })
createEntity(EntityType.BUCKET, { tableName: 'fox' }) // => No overload matches this call.
دو تماس اول بهخوبی کار میکنند، اما تماس سوم باعث خطای کامپایل میشود: «هیچ بارگذاری بیش از حدی با این تماس مطابقت ندارد».
اما چگونه میتوانم ایمنی نوع را درون تابع تضمین کنم؟ یعنی، مثال بالا در واقع کامپایل نمیشود زیرا «Property 'tableName' در نوع 'TableParams | BucketParams' وجود ندارد.»
آیا TypeScript نباید قادر باشد نوع آرگومان params را بر اساس اینکه کدام یک از دو اورلود تابع مطابقت دارد، استنتاج کند؟
پاسخ شماره ۱
میتوانید از نیاز به enum و استفاده نکردن از typeguard خلاص شوید با قرار دادن همه پارامترها در داخل یک نوع اینترفیس.
interface EntityData {
type: { new( ...args : any ) : BaseEntity };
// Add shared member params
}
interface TableData extends EntityData {
type: typeof TableEntity;
tableName: string;
}
interface BucketData extends EntityData {
type: typeof BucketEntity;
bucketName: string;
}
class BaseEntity {
constructor( data : EntityData ) {
//assign any shared member params here
}
}
class TableEntity extends BaseEntity {
public tableName : string;
constructor( data : TableData ) {
super(data);
this.tableName = data.tableName;
console.log('table name:',this.tableName);
}
}
class BucketEntity extends BaseEntity {
public bucketName : string;
constructor( data : BucketData ) {
super(data);
this.bucketName = data.bucketName;
console.log('bucket name:', params.bucketName);
}
}
function createEntity( data: TableData ): BaseEntity | undefined;
function createEntity( data: BucketData ): BaseEntity | undefined;
function createEntity( data: EntityData ): BaseEntity | undefined {
if ( data ) {
return new data.type(data );
}
return undefined;
}
createEntity( { type : TableEntity, tableName : 'foo'});
شما میتوانید با استفاده از این روش، مورد بالا را ایمنتر از نظر نوع کنید
let myTableData : TableData = {
type: TableEntity,
tableName : 'foo'
}
createEntity( myTableData );
// The above will compile
// The below 2 example will give compile errors
let myTableData2 : TableData = {
type: BucketEntity,
tableName : 'foo'
} // Will give type error for BucketEntity here
let myTableData3 : TableData = {
type: TableEntity,
bucketName : 'foo'
} // Will give error here for bucketName not existing in TableData
اگر هنوز هم بخواهید اوورلودها را، من از یک typeguard داخل سوئیچ استفاده میکنم تا به درستی تایپبندی شود
enum EntityType {
TABLE,
BUCKET
}
type TableParams = { tableName: string }
type BucketParams = { bucketName: string }
function createEntity (type: EntityType.TABLE, params: TableParams): void
function createEntity (type: EntityType.BUCKET, params: BucketParams): void
function createEntity (type: EntityType, params: TableParams | BucketParams): void {
switch(type) {
case EntityType.TABLE:
if( paramsAreTableParams( params ) ) {
console.log('table name:', params.tableName);
} else {
console.log('incorrect params');
}
break;
case EntityType.BUCKET:
if( paramsAreBucketParams( params ) ) {
console.log('table name:', params.bucketName);
} else {
console.log('incorrect params');
}
break;
}
}
function paramsAreTableParams( params : TableParams | BucketParams ) : params is TableParams {
return ( params as TableParams).tableName != undefined;
}
function paramsAreBucketParams( params : TableParams | BucketParams ) : params is BucketParams {
return ( params as BucketParams).bucketName != undefined;
}
