Issue
I'm creating a factory function, which takes a type argument to indicate the type of object to create, plus an parameter argument with parameters describing the object, which is dependent on the type of the object. A simplified example would look like this:
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
}
}
The function overloads ensure that users can only call the function with the right kind of parameters, depending on the entity type, e.g.
createEntity(EntityType.TABLE, { tableName: 'foo' })
createEntity(EntityType.BUCKET, { bucketName: 'bar' })
createEntity(EntityType.BUCKET, { tableName: 'fox' }) // => No overload matches this call.
The first two calls works just fine, but the 3rd call causes a compilation error: "No overload matches this call".
But how do I ensure type-safety inside the function? I.e. the example above doesn't actually compile because "Property 'tableName' does not exist on type 'TableParams | BucketParams'."
Shouldn't TypeScript be able to deduce the type of the params argument, based on which of the 2 function overloads matches?
Solution
You can get rid of needding the enum and not using a typeguard by putting all params inside of one interface type.
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'});
You can make the above more type safe by using it this way
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
If you still want the overloads I would use a typeguard inside of the switch so it is properly typed
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;
}
Answered By - Derek Lawrence
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.