Utilizing conditional generic types can help to differentiate them instead
enum ActionType {
create = 'create',
delete = 'delete',
update = 'update',
}
interface ActionHistoryDefault {
type: ActionType;
entityId: number | string | undefined
entityType: string;
}
interface ActionHistoryInsert extends ActionHistoryDefault {
type: ActionType.create
entityId: undefined;
}
type ActionHistoryOptions<T extends ActionType = any> = T extends ActionType.create ? ActionHistoryInsert : ActionHistoryDefault
declare const a: ActionHistoryOptions<ActionType.create>
a.entityId // undefined
declare const b: ActionHistoryOptions
b.entityId // number | string | undefined
If you do not specify anything to the generic types of ActionHistoryOptions
, it will default to ActionHistoryDefault
.
Playground
Thanks for @PiotrSzyma suggestion in the comment section.
Rather than utilizing a common type, consider having separate types for various actions. This way, your type checks will be accurately applied within if-statements
enum ActionType {
create = 'create',
delete = 'delete',
update = 'update',
}
interface ActionHistoryDelete {
type: ActionType.delete;
entityId: number | string | undefined
entityType: string;
}
interface ActionHistoryUpdate {
type: ActionType.update;
entityId: string;
}
interface ActionHistoryInsert {
type: ActionType.create
entityId: undefined;
}
type ActionHistory = ActionHistoryInsert | ActionHistoryUpdate | ActionHistoryDelete;
declare const a: ActionHistory
if (a.type === ActionType.create) {
a.entityId // undefined
}
if (a.type === ActionType.update) {
a.entityId // should be string
}
if (a.type === ActionType.delete) {
a.entityId // number | string | undefined
}
Playground