I am looking for a way to not provide an empty array for a generic function Parameter<F>
-typed parameter when F
does not receive parameters.
The following working example shows the current state
type A<F extends (...args: any[]) => any> = {
shouldPrintHello: boolean;
params: Parameters<F>;
};
const wrappingFunction = <F extends (...args: any[]) => any>(sentFunction: F, defaultParams: A<F>) => {
const innterFunction = (...args: Parameters<F>) => {
if (defaultParams.shouldPrintHello) console.log("hello");
sentFunction(args);
return;
};
const defaultValue = sentFunction(defaultParams);
return innterFunction;
};
const f1 = wrappingFunction(
(arg0: string) => {
return;
},
{ shouldPrintHello: true, params: ["defaultString"] }
);
const f2 = wrappingFunction(
() => {
return;
},
{ shouldPrintHello: true, params: [] }
);
f1("a string");
f2();
Desired (pseudo) code changes:
type A<F extends (...args: any[]) => any> = {
shouldPrintHello: boolean;
params: Parameters<F> === [] ? undefined : Parameters<F>;
};
const f2 = wrappingFunction(
() => {
return;
},
{ shouldPrintHello: true }
);
f2();
CodePudding user response:
Don't be afraid to use extends
to check for "equality":
type A<F extends (...args: any[]) => any> = {
shouldPrintHello: boolean;
params: Parameters<F> extends [] ? undefined : Parameters<F>;
};
Alternatively, you can check the length:
params: Parameters<F>["length"] extends 0 ? undefined : Parameters<F>;
CodePudding user response:
There's no built-in way to express that a property is optional if and only if some condition holds of its value, so if you want a type like that you have to write it yourself as a conditional type:
type OptionalParamsIfAllowedToBeEmpty<P> =
[] extends P ? { params?: P } : { params: P }
And then you can intersect it into the rest of your type definition:
type A<F extends (...args: any[]) => any> = {
shouldPrintHello: boolean;
} & OptionalParamsIfAllowedToBeEmpty<Parameters<F>>;
declare const wrappingFunction: <F extends (...args: any[]) => any>(
sentFunction: F,
defaultParams: A<F>) => (...args: Parameters<F>) => void
Let's test it out. First, let's make sure it behaves as desired for functions with at least one required parameter:
wrappingFunction(
(arg0: string) => {
return;
},
{ shouldPrintHello: true, params: ["defaultString"] }
);
wrappingFunction(
(arg0: string) => {
return;
},
{ shouldPrintHello: true } // error! missing params
);
Looks good, params
is not optional. Then let's test it for functions without any parameters:
wrappingFunction(
() => {
return;
},
{ shouldPrintHello: true }
);
wrappingFunction(
() => {
return;
},
{ shouldPrintHello: true, params: [] } // okay
);
wrappingFunction(
() => {
return;
},
{ shouldPrintHello: true, params: undefined } // okay
);
wrappingFunction(
() => {
return;
},
{ shouldPrintHello: true, params: ["defaultString"] } // error
);
Also looks good; you can leave out params
, or pass in undefined
or an empty array []
, but it won't let you pass in the wrong parameters. Finally, let's see how it behaves with a function with parameters that are all optional:
function f(x?: string) { }
wrappingFunction(f, { shouldPrintHello: false }) // okay
wrappingFunction(f, { shouldPrintHello: false, params: ["abc"] }) // okay
That's also what you want, I think. Since you can call f()
, you shouldn't be required to pass in params
in wrappingFunction
, but since you can also call f("abc")
, you should be allowed to pass in params
as ["abc"]
. This is an important difference between the [] extends P
check in OptionalParamsIfAllowedToBeEmpty<P>
and a seemingly similar P extends []
check; if P
is [x?: string]
, then [] extends [x?: string]
is true, but [x?: string] extends []
is false.