Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make a generic of a class is required?

Tags:

typescript

The class is:

class Test<P> {
  constructor(data: P) {}
}

I hope the following code does not pass the type check because it has no incoming generic:

new Test({ a: 1 })

I know that the generic P above is automatically derived as {a: number}, but this is not what I want, the following is.

new Test< {a: number} >({ a: 1 })

I tried a lot of methods, but in the end the generic P will be automatically derived into the constructor's parameter type.

like image 351
aweiu Avatar asked Aug 19 '19 14:08

aweiu


People also ask

How do you indicate that a class has a generic type parameter?

A generic type is declared by specifying a type parameter in an angle brackets after a type name, e.g. TypeName<T> where T is a type parameter.

How do you make a generic in C++?

Generics can be implemented in C++ using Templates. Template is a simple and yet very powerful tool in C++. The simple idea is to pass data type as a parameter so that we don't need to write the same code for different data types. For example, a software company may need sort() for different data types.


2 Answers

There is a question that deals with a very similar issue here:

async function get<U = void>(url: string & (U extends void ? "You must provide a type parameter" : string)): Promise<U> {
    return null as any;
}

The difference is that in that case the type parameter was not used in the parameters at all. This means typescript had no place to infer it the type parameter from. If, as in your case the type parameter is used in the argument list, typescript will use the argument as a source to infer the type parameter from and our trick of using the default value as a signal that no type parameter was explicitly specified will not work (since typescript not use the default if it can infer the type parameter).

The solution is simple enough, let typescript know that we don't want it to infer T from a specific parameter. While there is no builtin support to do this jcalz offers a reasonable workaround here

type NoInfer<T> = [T][T extends any ? 0 : never];
class Test<P = void> {
  constructor(data: NoInfer<P> & (P extends void ? "No type parameter was supplied" : {})) {}
}

new Test({ a: " "}) // err Argument of type '{ a: string; }' is not assignable to parameter of type 'void & "No type parameter was supplied"'.
new Test<{ a: string }>({ a: " "})// ok

play

like image 168
Titian Cernicova-Dragomir Avatar answered Oct 17 '22 23:10

Titian Cernicova-Dragomir


To update this for TypeScript 3.8.3 you can simplify this:

type NoInfer<T> = [T][T extends unknown ? 0 : never];

// Usage in a class:
class Test<P = "No type parameter was supplied"> {
  constructor(data: NoInfer<P>) {}
}

new Test({ a: " "}) // err Argument of type '{ a: string; }' is not assignable to parameter of type '"No type parameter was supplied"'.
new Test<{ a: string }>({ a: " "})// ok

// Usage in a function:
function Foo<P = "No type parameter was supplied"> (
  data: NoInfer<P>
) {
  return undefined
}

Play

like image 5
Tim Avatar answered Oct 18 '22 00:10

Tim