In TypeScript, there are no predefined existential types, which means you cannot simply specify "I am looking for any key key
from type T
."
To achieve this functionality, you can make use of generics by defining the Type
interface with two generic parameters T
and K extends keyof T
:
type Type<T, K extends keyof T> = {
key: K,
doStuff: (value: T[K]) => void
};
Then, you can create instances of Type
like so:
const Test: Type<{ var1: string, var2: number }, "var1"> = {
key: 'var1',
doStuff: (value) => { } // value is inferred as string
}
This approach works, but if manually specifying the key "var1"
bothers you, a workaround using currying can be implemented where one part is specified while the other is left to be inferred:
const typeFor = <T>() => <K extends keyof T>(type: Type<T, K>) => type;
// Manual specification of T
const typeForVar1StringVar2Number = typeFor<{ var1: string, var2: number }>();
// Inference of K
const Test2 = typeForVar1StringVar2Number({
key: 'var1',
doStuff: (value) => { } // value is inferred as string
});
If you require existential-like types due to having a union of literals in keyof T
, distributive conditional types can be utilized to handle such cases:
type PossibleTypes<T> = keyof T extends infer K ?
K extends any ? Type<T, K> : never : never;
An array containing possible types can be created using the above definition:
type ArrayOfPossibleTypes<T> = Array<PossibleTypes<T>>
const asArrayOfPossibleTypes = <T>(arr: ArrayOfPossibleTypes<T>) => arr;
const testArray = asArrayOfPossibleTypes<{ var1: string, var2: number }>([
{
key: 'var1', doStuff(value) { /* value is string */ }
}, {
key: 'var2', doStuff(value) { /* value is number */ }
]
);
If all else fails, there is an implementation of existential types in TypeScript involving continuation passing, although it might be more complex than needed for your situation:
type ExistentialType<T> = <R>(f: <K extends keyof T>(x: Type<T, K>) => R) => R;
You can define and utilize an existential type as demonstrated below:
const exType: ExistentialType<{ var1: string, var2: number }> =
(f) => f({ key: 'var1', doStuff(value) { } });
The usage of existential types through continuation passing may be excessive for your requirements.
I hope these explanations provide clarity and assistance. Best of luck with your TypeScript endeavors!