I think it's clearer to try it on TS Playground:
function identity<T extends (...args: any[]) => any>(fn: T): T {
return fn;
}
function fn(args: {
cb: (foo: number) => void,
}) {}
fn({
cb: identity((foo /* infers number */) => {}),
});
function fn2(args: {
cb?: (foo: number) => void,
}) {}
fn2({
cb: identity((foo /* doesn't infer number */) => {}),
});
function fn3(args: {
cb: (foo: number) => void,
} | {}) {}
fn3({
cb: identity((foo /* infers number */) => {}),
});
For fn
and fn3
, TS was able to infer that foo
is a number
. However, for fn2
, TS just typed foo
as any
. The typing for fn2
and fn3
are functionally the same thing, so I'm wondering why TS couldn't infer the type of foo
.
The realistic use-case for this is React's useCallback
, I was trying to infer the argument types for functions that pass through useCallback
.
Why does TS behave like this? Is there a less hacky solution?
In Typescript, making optional parameters is done by appending the “?” at the end of the parameter name in the function when declaring the parameters and the parameters which are not marked with “?” i.e not optional parameter are called as default parameters or normal parameters where it is must and compulsory to pass ...
TypeScript provides a Optional parameters feature. By using Optional parameters featuers, we can declare some paramters in the function optional, so that client need not required to pass value to optional parameters.
Use the parameter?: type syntax to make a parameter optional. Use the expression typeof(parameter) !== 'undefined' to check if the parameter has been initialized.
TypeScript infers types of variables when there is no explicit information available in the form of type annotations. Types are inferred by TypeScript compiler when: Variables are initialized. Default values are set for parameters.
I think the crucial step here is the inference of identity
s return type:
let result: ((foo: number) => void | undefined) = identity(...);
As the return type has a base constraint of (...args: any[]) => any
this rule of the Typescript specification applies:
The inferred type argument for each type parameter is the union type of the set of inferences made for that type parameter. However, if the union type does not satisfy the constraint of the type parameter, the inferred type argument is instead the constraint.
~ TypeScript Language Specification (outdated), Contextual Signature Instantiation
As undefined
(the optional value) does not satisfy the constraint, the constraint is taken, and T gets inferred as (...args: any[]) => any
.
By removing the constraint, T gets inferred correctly
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With