0

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?

2
  • Why using a union type for set? Commented Jul 13, 2022 at 19:30
  • set is just a variable name - it does not matter, there is no more meaning behind it Commented Jul 13, 2022 at 20:39

1 Answer 1

2

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.

Playground


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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.