Reputation: 9742
I have the following interface
ITle {
type: "A" | "B";
id: number;
}
I want to use this interface to type the arguments of a function:
const exampleFunc = (type, id) => {};
Is there a straightforward way to do this? Thanks!
Upvotes: 0
Views: 334
Reputation: 330086
There's no reliable programmatic way to turn ITle
into (x: "A" | "B", y: number) => void
instead of (x: number, y: "A" | "B") => void
.
TypeScript generally considers two object types that differ only in the declared order of their properties to be identical types. For example:
interface ITle {
type: "A" | "B";
id: number;
}
var foo: ITle;
var foo: { id: number, type: "A" | "B" }; // no error here
Note that the redeclaration of foo
does not result in an error, which means the compiler considers those two annotated types to be identical. For that reason, you can't reliably observe the order of properties in TypeScript object types.
And since you can't reliably turn ITle
into an ordered list like ["type", "id"]
, you can't programmatically turn it into a function type where the parameters are in a specific order.
(You may have noticed that I keep saying "reliably". It is possible to tease property order out of an object type in TypeScript, but your really shouldn't do it and there are many many caveats. See this Q/A for more information. For this question I will assume it's impossible).
So what can you do? Well, if you tell the compiler what order you want, it will work. This requires the object-to-function transformation to take a tuple of keys, like this:
type ToFunc<T, K extends Array<keyof T>> =
(...args: { [I in keyof K]: T[Extract<K[I], keyof T>] }) => void;
Now we can test it on ITle
:
type Func = ToFunc<ITle, ["type", "id"]>;
/* type Func = (args_0: "A" | "B", args_1: number) => void */
const exampleFunc: Func = (type, id) => {
console.log(type.toLowerCase(), id.toFixed(1));
};
exampleFunc("A", 123); // a, 123.0
That works as desired, hooray!
One more thing. Note that the output type looks like (args_0: "A" | "B", args_1: number) => void
instead of something like (type: "A" | "B", id: number) => void
. That is, the function type's parameters' names do not correspond to the property names from the function. That's because, like property order, function types' parameter names are not observable. Two function types that differ only by parameter names are considered to be identical types:
var bar: Func;
var bar: (type: "A" | "B", id: number) => void;
var bar: (fooby: "A" | "B", dooby: number) => void;
Function parameter names in TypeScript types are useful only as documentation hints and have no effect on the behavior of the type system. If you really care about the names, (and I don't think you should), you can use a labeled tuple to tell the compiler what labels to give the function parameters:
type FuncLabeled = ToFunc<ITle, [type: "type", id: "id"]>;
/* type FuncLabeled = (type: "A" | "B", id: number) => void */
But this might be a bit too much, really, since at this point you're writing each property name out three times (once for the interface and twice for the tuple).
Honestly, unless your ITle
type has scads of properties in it or its property types are likely to change often, the effort involved in making the compiler do this work for you is unlikely to be worth it. You might as well just write out the type (type: "A" | "B", id: number) => void
by hand.
Upvotes: 2
Reputation: 2082
How about:
ITle {
type: "A" | "B";
id: number;
}
const exampleFunc = ({type, id}: ITle) => {};
It'll force you to call the function with an object, but I usually find that more readable: exampleFunc({type: "A", id: 1})
UPDATE: Alternatively you could type the function as
type ExampleFunction = (type: 'A' | 'B', id: number) => void;
const exampleFunc: ExampleFunction = (type, id) => {};
Upvotes: 1