Consider a library which exports a run
function like below:
runner.ts
export type Parameters = { [key: string]: string };
type runner = (args: Parameters) => void;
export default function run(fn: runner, params: Parameters) {
fn(params);
}
And consider the following code in a separate file:
index.ts
import type { Parameters } from "./runner.ts";
import run from "./runner.ts";
type CustomParams = { hello: string };
function logGenericArgs(args: Parameters): void {
console.log(args);
}
function logHelloFromArgs(args: CustomParams): void {
console.log(args.hello);
}
run(logGenericArgs, { abc: "123" });
run(logHelloFromArgs, { hello: "123" }); /* Argument of type '(args: CustomParams) => void' is not assignable to parameter of type 'runner'.
Types of parameters 'args' and 'args' are incompatible.
Property 'hello' is missing in type 'Parameters' but required in type 'CustomParams'. ts(2345)
*/
Why does TypeScript complain about the different types, when they're perfectly compatible with each other? From my understanding, type Parameters
is a generic object with string
keys and string
values; CustomParams
's "hello" key perfectly fits the Parameters
's type signature.
How can I make the code in the "runner" library to accept a generic object type, and work nicely with other types that are compatible?
I do not want to use type unknown
or any
, as that's basically useless. I want the call signature of the run
function to express that args
is an object, but I do not want to limit the args
' type to that specific signature only.
I also do not want to specify the hello
key in type CustomParams
as optional, because that key should not be optional in the usage of the type CustomParams
- and I do not want to add the key hello
in type Parameters
because it is not required in every use case of the "runner" library.
CodePudding user response:
TypeScript doesn't know that params
is supposed to be the parameters of fn
, but you can easily fix that but adding a generic parameter to associate the two:
type Params = { [key: string]: string };
type runner<P extends Params = Params> = (args: P) => void;
function run<P extends Params>(fn: runner<P>, params: P) {
fn(params);
}
Now when you call your custom params function:
run(logHelloFromArgs, { hello: "123" });
It's inferred as
function run<{
hello: string;
}>(fn: runner<{
hello: string;
}>, params: {
hello: string;
}): void
which is what we want.