How to use union type as function parameter
I the example below I have created a union type (string or number). Then I have created a function signature which should accept as input a string or number. But now I cannot use this function definition
type AB = string | number
type X = (inp: AB) => void;
const x: X = (inp: number) => { console.log('ok')}; // <-- ERROR
const y: X = (inp: string) => { console.log('ok')}; // <- ERROR
Because the constants x and y have errors
UPDATED DEMO
The type AB
can change, more types can be added, so I would be nice if I could use it in defining type X
and then create implementation for each type defined in AB
. But how can I do this?
By using a generic type parameter with X
, you won't even need to type the argument in the implementations:
TS Playground
type AB = string | number;
type X<T extends AB> = (inp: T) => void;
const x: X<number> = (inp) => {
inp // number
};
const y: X<string> = (inp) => {
inp // string
};
Edit: Response to new question:
Note that idiomatically-named string enums use PascalCase.
See comments in code:
TS Playground
// Base types:
/**
* From [Distributive conditional types](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types):
*
* > ...an instantiation of `T extends U ? X : Y` with
* > the type argument `A | B | C` for `T` is resolved as
* > `(A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)`.
*
* For `T extends unknown ? ...`, see also:
* https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#example-3
*/
type DistributeUnionToFirstParamInFn<
U,
FnReturnType = void,
> = U extends unknown ? ((_: U) => FnReturnType) : never;
type AB = string | number;
const enum Z { X = 'x', Y = 'y'};
// First example:
type MyObj = Record<Z, DistributeUnionToFirstParamInFn<AB>>;
const obj1: MyObj = {
x: (inp: string) => { console.log('this is a string ', inp) },
y: (inp: number) => { console.log('this is a number ', inp) },
};
type ParamInObj1X = Parameters<typeof obj1['x']>[0]; // string | number
type ParamInObj1Y = Parameters<typeof obj1['y']>[0]; // string | number
// So, not quite what you want. Compare with...
//Second example:
function validateObject <T extends Record<Z, DistributeUnionToFirstParamInFn<AB>>>(value: T): T {
return value;
}
validateObject({x: (inp: string) => { console.log('this is a string ', inp) }}); /*
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Property 'y' is missing in type... Error (2345) */
validateObject({
x: (inp: string) => { console.log('this is a string ', inp) },
y: (inp: boolean) => { console.log('this is a number ', inp) }, /*
^
Type '(inp: boolean) => void' is not assignable to type '((_: string) => void) | ((_: number) => void)'... Error (2322) */
});
const obj2 = validateObject({
x: (inp: string) => { console.log('this is a string ', inp) },
y: (inp: number) => { console.log('this is a number ', inp) },
});
type ParamInObj2X = Parameters<typeof obj2['x']>[0]; // string
type ParamInObj2Y = Parameters<typeof obj2['y']>[0]; // number
// 👍