Reputation: 7025
I have this interface:
export interface AlertInteraction<T> {
id: AlertId;
data: T;
buttonId: AlertButtonId;
}
But there are times where data is not needed. I know I can declare it as data: T
but I would like to know if I could something like this:
export interface AlertInteraction<T> {
id: AlertId;
data: T;
buttonId: AlertButtonId;
}
export interface AlertInteraction {
id: AlertId;
buttonId: AlertButtonId;
}
So, if I give T
then I assume I want to access the data, if no then assume it does not exist. Is it possible?
Upvotes: 12
Views: 8657
Reputation: 20162
In order to achieve that we need to use conditional types.
type AlertId = string; // just example type alias
type AlertButtonId = string; // just example type alias
type AlertInteraction<T = undefined> = {
id: AlertId;
buttonId: AlertButtonId;
} & (T extends undefined ? {} : {
data: T;
})
// example usage
// no need for data (T is undefined)
const a: AlertInteraction = {
id: '1',
buttonId: '1'
}
// data prop is required
const b: AlertInteraction<number> = {
id: '1',
data: 1,
buttonId: '1'
}
Most important is this part:
& (T extends undefined ? {} : {
data: T;
})
We join conditionally or type {}
if T is assignable to undefined, or we join {data: T}
for other type. In result if we don't set the generic, it is by default undefined
and our type constructor computes into:
// For T == undefined
{
id: AlertId;
buttonId: AlertButtonId;
} & {}
// what is equal to just:
{
id: AlertId;
buttonId: AlertButtonId;
}
For a case where argument(generic) is provided as something different then undefined
, then the final type has a form:
// For T != undefined
{
id: AlertId;
buttonId: AlertButtonId;
} & { data: T}
// which is equal to
{
id: AlertId;
buttonId: AlertButtonId;
data: T;
}
Where T
is type given by argument of type constructor. To also clear the information - Type constructor = any type definition with an generic argument.
Upvotes: 14
Reputation: 11577
Create a second wrapping type that when it is provided a type it doesn't omit data but when there is no type provided, then data field is omitted.
As you stated, making data optional won't be the right thing as you need the field to not exist at all when you don't need it.
export interface IInterationData<T> {
id: AlertId;
data: T;
buttonId: AlertButtonId;
}
type IAlertInteraction<T = undefined> = T extends undefined
? Omit<IInterationData<any>, "data">
: IInterationData<T>;
export const a: IAlertInteraction = {
id: 1,
buttonId: ""
};
export const b: IAlertInteraction<number> = {
id: 1,
data: 2,
buttonId: ""
};
Upvotes: 2
Reputation: 439
You can try something like this
export interface AlertInteraction<T = any> {
id: AlertId;
data?: T;
buttonId: AlertButtonId;
}
// those are valid:
const x: AlertInteraction = {id: 'AlertId', buttonId: 'AlertButtonId'};
// Accept the data property as number only.
const x: AlertInteraction<number> = {id: 'AlertId', buttonId: 'AlertButtonId', data: 123};
Or you can make them 2 interfaces , one without data
property and generic param and the second extend the first one and has the data property and generic param
something like this
export interface AlertInteractionBase {
id: string;
buttonId: string;
}
export interface AlertInteraction<T> extends AlertInteractionBase {
data: T;
}
Upvotes: -1