It is not necessary to specify a generic type on a Typescript class

When working with Typescript and default compiler options, there are strict rules in place regarding null values and uninitialized class attributes in constructors. However, with generics, it is possible to define a generic type for a class and create a new instance of the class without specifying the type!

class Foo<T> {
    bar(item: T): void {
        console.log('typeof T: ', typeof item)
    }
}

const foo1 = new Foo<string>() // Type 'T' specified
foo1.bar('hello')
foo1.bar(6) // error TS2345: Argument of type '6' is not assignable to parameter of type 'string'

const foo2 = new Foo() // Type 'T' not specified
foo2.bar('hello')
foo2.bar(6) // Works without error

Could new Foo() be considered incorrect?

In this case, I am using default compiler options that do not allow adding an extra attribute a: T which would never be initialized.

Answer №1

To prevent the omission of T from causing an error in the constructor, you can utilize conditional type magic along with a constructor that requires at least one argument.

If no argument is provided, you can render the class unusable by setting a default for the type parameter. Setting the default to never will effectively achieve this.

class Foo<T = never> {
    bar(item: T): void {
        console.log('typeof T: ', typeof item)
    }
}

const foo1 = new Foo<string>() // Specified T
foo1.bar('hello')
foo1.bar(6) // Error TS2345: Argument of type '6' is not assignable to parameter of type 'string'

const foo2 = new Foo() // Missing T
foo2.bar('hello') // Error
foo2.bar(6) // Error 

An alternative approach is to use a constructor overload and rest parameters with tuples. This will result in an error if the type parameter is omitted (meaning it defaults to never).

class Foo<T = never> {
    constructor(...a: T extends never ? ['No T was specified']:[])
    constructor() {

    }
    bar(item: T): void {
        console.log('typeof T:', typeof item)
    }
}

const foo1 = new Foo<string>() // Specified T
foo1.bar('hello')
foo1.bar(6) // Error TS2345: Argument of type '6' is not assignable to parameter of type 'string'

const foo2 = new Foo() // Missing T, error!
foo2.bar('hello') // Error
foo2.bar(6) // Error

Answer №2

In addition to the previous responses, it is worth mentioning that TSLint has a specific guideline addressing this issue: no-inferred-empty-object-type

Implementing this guideline will prompt TSLint to flag instances where TypeScript assumes {} as a generic type (similar to the noImplicitAny compiler option, but for generics).

This rule appears to align with your requirements.

Answer №3

One interesting feature is the ability to create a new class without explicitly defining its type when a generic type is already set for the original class!

This concept is known as type argument inference:

This means that the compiler can automatically determine the value of T based on the type of the argument we provide.

Therefore

Is it considered incorrect to create a new Foo() without specifying the type?

No, because the compiler may assign T as any or string | number depending on your existing code, making T perfectly valid.

Considering your code:

const foo2 = new Foo() // T missing
foo2.bar('hello')
foo2.bar(6) // no issues

Why would there be any problems? The compiler needs to infer a type by analyzing all instances to determine a suitable type. What sets apart the previous snippet from this one:

const foo3 = new Foo() // T missing
foo3.bar(6) 
foo3.bar('hello') // no complaints

The only distinction lies in the sequence of actions. Why should the compiler assume that only the first method usage defines the type of T? Consider this scenario:

const foo3 = new Foo() // T missing
if (someValueOrBooleanReturningFunction) {
  foo3.bar(6) 
}
foo3.bar('hello') // no issues

What then? It could or couldn't ascertain the if condition's value, but since no type is specified, the compiler must deduce the value of T.

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

The correct way to incorporate a global property into a component template (using Vue 3, Vite, TypeScript, and the Composition API)

The component's property is not functioning properly https://i.sstatic.net/qaUG9.png src/main.ts import { createApp } from 'vue' import languagePlugin from '@/plugins/languagePlugin' import App from './App.vue' const a ...

How to efficiently filter an array containing nested objects using TypeScript

I'm currently working with a list of menus and submenus: [ { "NUA_ID_Menu": 1, "TXT_Nom_Menu": "Menu 1", "Liste_Sous_Menus": [ { "TXT_Nom_Menu": ...

Is it necessary for me to set up @types/node? It appears that VSCode comes with it pre-installed

Many individuals have been adding @types/node to their development dependencies. Yet, if you were to open a blank folder in VSCode and create an empty JavaScript file, then input: const fs = require('fs'); // <= hover it and the type display ...

Angular 6 combined with Firebase is experiencing difficulties with routing after a successful login

After spending hours trying to fix my issue, I still can't figure it out. I've searched through related threads on SO, but haven't found a solution yet. Issue After successfully signing up, the email verification flag is set to true. Howev ...

The concept of TypeScript usage within the `mui-x` DataGrid while calling the `useGridApiRef()` function

Could someone please help me understand the syntax used in this code snippet from mui/mui-x? export declare const useGridApiRef: <Api extends GridApiCommon = GridApiPro>() => React.MutableRefObject<Api>; My interpretation is that it exports ...

Identifying the scenario where Partial<T> inherits from T

I am facing a scenario where I am working towards achieving a specific "state": type State = { foo: number, bar: number, baz?: string }; Initially, I may not have reached the complete State yet but rather align with the structure of Partial<State>. ...

Comparing attribute selectors to class selectors in the realm of styling

I often come across code that looks like this: <input type="text" class="class1" name="text"> <input type="submit" class="class2" name="submit"> which is then styled as follows: input[type=text] { styles here...} input[type=submit] { sty ...

Angular - Enabling the next screen button only after completing multiple selections

Currently, I'm working on a screen where users can select multiple options from a table. The requirement is that they must select at least 3 options before they can proceed. However, I am facing difficulties in implementing this functionality and unsu ...

I am unfamiliar with this scenario but I can utilize Axios, async/await, and TypeScript to navigate it

Having trouble creating a workflows list from an axios response Error: Argument of type 'Promise<unknown>' is not assignable to parameter of type 'SetStateAction<WorkflowForReactFlowProps[] | null>'. Here's the Axios c ...

What is the proper way to specify the type for a <video> element reference in React when utilizing Typescript?

I have been experimenting with controlling the play/pause state of a video in React.js using ref's. My code functions correctly but I am encountering tslint errors that I am currently trying to diagnose: function App() { const playVideo = (event:a ...

How can I pass DOCUMENT in Angular?

In my directive, I use dependency injection to access the DOCUMENT and set up an event listener: constructor(@Inject(DOCUMENT) private document: Document) {} ngOnInit() { this.document.addEventListener('click', this.clicked, true); } @Bound ...

Jest is having trouble recognizing a custom global function during testing, even though it functions properly outside of testing

In my Express app, I have a custom function called foo that is globally scoped. However, when running Jest test scripts, the function is being recognized as undefined, causing any tests that rely on it to fail. This is declared in index.d.ts: declare glob ...

React Router malfunctioning on production environment when integrated with an Express backend

My Single Page application is built using React for the frontend and Express for the backend. Within the application, there are two main components: and . The goal is to display the component when the "/"" URL is requested, and show the component for an ...

Nativescript encountered an issue while attempting to generate the application. The module failed to load: app/main.js

I'm currently experimenting with the sample-Groceries application, and after installing NativeScript and angular 2 on two different machines, I encountered the same error message when trying to execute: tns run android --emulator While IOS operations ...

Documentation for npm package that has been published

Recently, I created my very first npm package using TypeScript. However, when I tried to use this package in another project, I realized that I wasn't getting the expected code completion and it was challenging to work with it without proper support. ...

Angular input box with integrated datepicker icons displayed inside

Currently, I have an input field and a datepicker displayed in a row. However, I need to show an icon inside the input box instead. Here is my code: <div class="mb-2" style=" float: left;" class="example-full-width" class= ...

What could be causing my TSC to constantly crash whenever I try to utilize MUI props?

Currently in the process of converting a JavaScript project using Next.js and Material UI to TypeScript. This is a snippet of code from one of my components. Whenever I include Props as an intersection type along with MUI's BoxProps, the TypeScript c ...

What is the best way to convert JSON into a complex object in Typescript and Angular?

In my Typescript class for an Angular version 5 project, I have a JavaScript function that generates a style object. Here is the function: private createCircle(parameters: any): any { return new Circle({ radius: parameters.radius, ...

Consecutive HTTP requests in Angular using rxjs

Currently working on a function that utilizes concatMap to perform sequential HTTP calls, such as adding a person, using the returned information to add a contact, and then adding some accounts. This function takes in a list (in my case, portfolios) and f ...

Guide to simulating Twilio with Jest and TypeScript to perform unit testing

Please assist me in mocking a Twilio service that sends messages using Jest to mock the service. Below is the code I am working with: import { SQSEvent } from "aws-lambda"; import { GetSecretValueResponse } from "aws-sdk/clients/secretsmanag ...