Ensuring data types for an array or rest parameter with multiple function arguments at once

I am looking for a way to store various functions that each take a single parameter, along with the argument for that parameter.

So far, I have managed to implement type checking for one type of function at a time. However, I am seeking a solution that allows me to pass in multiple types of functions simultaneously and still receive the correct type checking for each function's argument.

type WalkOptions = {
    x: number
    speed: number
}

type JumpOptions = {
    y: number
    speed: number
}

type ShootOptions = {
    type: string
    duration: number
}

const walk = (options: WalkOptions)=>{}
const jump = (options: JumpOptions)=>{}
const shoot = (options: ShootOptions)=>{}

type Action<T extends (options: any) => void> = {
    command: T
    args: Parameters<T>[0]
}

function add<T extends (options: any) => void>(...actions: Action<T>[]){}

With the code above, I am able to use

add({command: walk, args:{x: 10, speed: 1}})
and obtain accurate type checking on the arguments. Similarly, using
add({command: walk, args:{x: 10, speed: 1}}, {command: walk, args:{x: -10, speed: 2}})
works flawlessly.

However, if I try

add({command: walk, args:{x: 10, speed: 1}}, {command: jump, args:{x: 5, speed: 1}})
, I encounter a type error because it expects
(options: WalkOptions) => void
instead of
(options: JumpOptions) => void
.

Is there a way to utilize add in this manner while still receiving type checking on the arguments specific to each command?

add({
    command: walk,
    args:{
        x: 10,
        speed: 1
    }
},
{
    command: jump,
    args:{
        y: 5,
        speed: 1
    }
},
{
    command: shoot,
    args:{
        type: "laser",
        duration: 3
    }
},
{
    command: walk,
    args:{
        x: -10,
        speed: 2
    }
})

Additionally, is there a way to create an array of these Actions with type checking, which can then be used with add like add(...actionArray)?

Answer №1

When working with TypeScript, it's important to note that the inference of union types can sometimes lead to unexpected behavior. In the case of generic type arguments like T, it's actually beneficial that TypeScript doesn't automatically infer a union type.

To ensure that parameters are not mixed together and maintain clear separation, you can make your function generic in the tuple type of these type arguments. By using a mapped tuple type, you can then convert this into the type of actions:

function add<T extends ((options: any) => void)[]>(
  ...actions: { [I in keyof T]: Action<T[I]> }
) { }

Instead of having T as A | B | C, think of it as [A, B, C]. This way, actions will be of type

[Action<A>, Action<B>, Action<C>]
.

Test the concept with different scenarios:

add(
  { command: walk, args: { x: 10, speed: 1 } },
  { command: jump, args: { y: 5, speed: 1 } },
  { command: shoot, args: { type: "laser", duration: 3 } },
  { command: walk, args: { x: -10, speed: 2 } }
); // This is acceptable
/* Inference for T will be [
  (options: WalkOptions) => void, 
  (options: JumpOptions) => void, 
  (options: ShootOptions) => void, 
  (options: WalkOptions) => void
] */

If there is an error in the structure, TypeScript will catch it:

add(
  { command: walk, args: { x: 10, speed: 1 } },
  { command: shoot, args: { y: 5, speed: 1 } }, // error!
  // ---------------------> ~  
  // Object literal may only specify known properties, 
  // and 'y' does not exist in type 'ShootOptions'.
  { command: shoot, args: { type: "laser", duration: 3 } },
  { command: walk, args: { x: -10, speed: 2 } }
);
/* Inference for T will be [
  (options: WalkOptions) => void,
  (options: ShootOptions) => void, 
  (options: ShootOptions) => void, 
  (options: WalkOptions) => void
] */

Feel free to explore and test the code on the Playground link for more experimentation

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Discovering the process of reaching service members through an HTML View

Currently, I am in the process of learning Angular 2 and find myself unsure about the most efficient way to update the view. For instance, let's say I have two components: User and Main. The User component retrieves a list of users from the UserServ ...

The production build for Angular 9 Keyvalues pipe fails to compile

After successfully running my app on the development server with ng serve, I encountered a failure when trying to build it for production. The error message that popped up was: ERROR in src/app/leaderboard/leaderboard.component.html(9,17): Argument of typ ...

What is the best way to implement an asynchronous function using a for loop and APIs in Typescript?

Struggling with ensuring my function returns data only after completing all API calls and the for loop. getListingsImages(sessionID, mlsSearchCriteria){ this.http.get(this.laconiaBaseURL + "mls/search/" + sessionID + "?" +querySt ...

Is there a way to have my accordion adjust automatically?

I have developed a dynamic accordion component that populates its values from the parent component. However, I am facing an issue where each accordion does not respond individually to clicks. Whenever I click on any accordion, only the first one expands an ...

What is the best way to merge two interfaces and convert all of their fields to optional properties?

I have two unalterable interfaces: interface Person { name: string; age: number; } interface User { username: string; password: string; } I aim to merge them into a single interface called Player // please, adjust this code accordingly interfac ...

In TypeScript, what is the return Type of sequelize.define()?

After hearing great things about TypeScript and its benefits of static typing, I decided to give it a try. I wanted to test it out by creating a simple web API with sequelize, but I'm struggling to understand the types returned from sequelize. Here ar ...

Is using instanceof the most effective method to verify type in Angular?

When working with the model, we import Type1 and Type2. Using the TypeComp variable which is a union of Type1 and Type2. import { Type1, Type2 } from '.'; export type TypeComp = Type1 | Type2; In the some.component.ts file, the selectedData$ obs ...

Focusing on an input element in Angular2+

How do I set focus on an input element? Not with AngularDart, but similar to the approach shown in this question: <input type="text" [(ngModel)]="title" [focus] /> //or <input type="text" [(ngModel)]="title" autofocus /> Does Angular2 provi ...

Enhance your text in TextInput by incorporating newline characters with advanced editing features

I'm encountering an issue with my Textarea component that handles Markdown headers: type TextareaProps = { initValue: string; style?: StyleProp<TextStyle>; onChange?: (value: string) => void; }; type OnChangeFun = NativeSynthetic ...

Choose one of the options provided by the radio button that best fits the question using Playwright

Can Playwright docs be used to select a radio button answer based on the question and answer? I need to answer a series of yes/no questions. Here's the HTML structure of the survey site: <!DOCTYPE html> <html lang="en"> <bod ...

Property '{}' is not defined in type - Angular version 9.1.1

Currently, I am working with Angular CLI version 9.1.1 and I am attempting to update certain data without updating all of it. form: UserInfo = {adresse : {}}; UserInfo.interface export interface UserInfo { id_user: string; username: string; em ...

Creating UI Bootstrap dropdowns using ng-repeat on the fly

As a newcomer to AngularJS and UI Bootstrap, I am facing an issue with adding dropdowns dynamically using ng-repeat. The main problem lies in the fact that when one dropdown is clicked, it triggers all of them simultaneously. It seems like there is some mi ...

Tips for customizing the list of components and attributes for each component in the Angular Form.io builder

I have successfully integrated form.io with Angular 10. After creating a demo project using form.io in the Angular CLI, I was able to develop a custom component and customize the editForm for it. import { Injector } from '@angular/core'; import ...

Steps for transitioning a VUE JS project to TypeScript

Is it possible to transition a VUE JS project from JavaScript to TypeScript without rewriting everything? I heard from a friend that it can be done through the VUE CLI, but I haven't been able to find any documentation or articles on this method. Has ...

The React-widgets DateTimePicker is malfunctioning and displaying an error stating that it cannot assign to the type of 'Readonly<DateTimePickerProps>'

Hello there! I am a newcomer to TypeScript and I am facing difficulty in understanding an error. Any help regarding this will be greatly appreciated. I have tried searching for solutions online and even attempted changing the version, but I am unsure of wh ...

The power of Vue reactivity in action with Typescript classes

Currently, I am working on a Vue application that is using Vue 2.6.10 along with Typescript 3.6.3. In my project, I have defined a Typescript class which contains some standard functions for the application. There is also a plugin in place that assigns an ...

Combine arrays using union or intersection to generate a new array

Seeking a solution in Angular 7 for a problem involving the creation of a function that operates on two arrays of objects. The goal is to generate a third array based on the first and second arrays. The structure of the third array closely resembles the f ...

Vuejs fails to properly transmit data

When I change the image in an image field, the new image data appears correctly before sending it to the back-end. However, after sending the data, the values are empty! Code Commented save_changes() { /* eslint-disable */ if (!this.validateForm) ...

Identifying JavaScript Errors in a Web Page: A Guide to Cypress

Currently, I am working on creating a spec file that contains N it(s), with each it visiting a specific page within the application and returning the number of errors/warnings present. I have also shared my query here: https://github.com/cypress-io/cypres ...

A Defer statement in TypeScript that mimics Go's functionality

Is there an equivalent to Go's Defer statement in TypeScript? I find it tedious to write cleanup code in various parts of a function. Searching for a simpler alternative. I tried searching on Google, but couldn't locate any relevant information ...