I wrote some typescript code and I am not quiet sure why it is not working as I expected. I would really appriopriate if someone would explain me what I do wrong or what other approach should I use.
Let's say I have a Definition
of a function name
and data
.
interface Definition<TName extends string, TData extends any> {
name: TName;
data: TData;
}
Definition
at some point changes into Action
. Action
has a function named method
with payload
as first paramater type.
interface Action<TDefinition extends Definition<string, any>> {
name: TDefinition["name"];
method: (data: TDefinition["data"]) => void;
}
Keep in mind that all Definition
union elements must move through Changer
.
interface Changer<TDefinition extends Definition<string, any>> {
actions: Action<TDefinition>[];
}
interface Definition<TName extends string, TData extends any> {
name: TName;
data: TData;
}
interface Action<TDefinition extends Definition<string, any>> {
name: TDefinition["name"];
method: (data: TDefinition["data"]) => void;
}
interface Changer<TDefinition extends Definition<string, any>> {
actions: Action<TDefinition>[];
}
const set: Changer<Definition<"one", 1> | Definition<"two", 2>> = {
actions: [
{
name: "one",
method: (data) => data, // data type is equal to '1 | 2' I would expect it to be '1'
},
],
};
set.actions[0].method(1); // data type is equal to '1 | 2' I would expect it to be '1'
Could you tell me what should I do to achieve result I expect?
I am thinking new Definition
, however I belive I may have some issues because of [key: string]: any
.
interface Definition<TName extends string, TData extends { [key: string]: any }> {
[key in TName]: TData[key]
}
What do you think?
CodePudding user response:
This can be solved by using distributive conditional types.
interface Changer<TDefinition extends Definition<string, any>> {
actions: (
TDefinition extends infer U extends Definition<string, any>
? Action<U>
: never
)[];
}
We basically want to change the type of actions
from
actions: Action<Definition<"one", 1> | Definition<"two", 2>>[]
to the type
actions: (Action<Definition<"one", 1>> | Action<Definition<"two", 2>>)[]
This can be done by forcing the union elements of TDefinition
to be distributed by a conditional to individual Actions
.
Now we have the correct type for the parameters in the functions.
const set: Changer<Definition<"one", 1> | Definition<"two", 2>> = {
actions: [
{
name: "one",
method: (data) => data, // data: 1
},
{
name: "two",
method: (data) => data // data: 2
}
],
};
But this statement won't work:
set.actions[0].method(1); // Argument of type 'number' is not assignable to parameter of type 'never'
The set
variable simply does not have information about individual array elements because you gave it an explicit type. To solve this you could create the set
with a generic function.