“This is the sixth day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

The cause of

Although I have been learning TypeScript on and off, I have only recently tried to use it for a real project development. During the development process, my colleague A Yang soon ran into this problem:

I want to have a method that I pass in that can instantiate from the class THAT I pass in;

If implemented with native JS, it would look like this:

function test(fn){
  const _instance = new fn();
}
Copy the code

Having done some JAVA development in my early years, I first came up with the corresponding syntax in my mind:

  private void test(Class clazz) throws Exception {
    Object _instance = clazz.newInstance();
  }
  // The following is used
  test(Cat.class)
Copy the code

Then, with the initial belief that my colleague’s problem was my problem, I took a quick look at the TS official documentation – Basic Types section. Huh? I knew there was more to it than that. There is no such thing as Type or Class in the base Type. Should not be, if you do not support this feature, it is estimated that TS would have been a complete mess; So in TS, what should this syntax look like?

Find a solution

As I just got to know TS and had a limited understanding of its language features, I decided to read the source code of the open source project for reference. Soon, I found a similar scenario in nest’s source code, and found a real solution;

export interface Type<T = any> extends Function {
  new(... args:any[]): T;
}
Copy the code

Usage:

function test(fn: Type) :void {
  const a = new Type();
}

// The following is used
test(Cat);
Copy the code

It can even support inheritance;

function test(fn: Type<Animal>) :void {
  const a = new Type();
}

class Animal {... }class Cat extends Animal {... }// The following is used
test(Cat);
Copy the code

OK, I found a working snippet, but what does this snippet mean? What knowledge points do they use in TS official documentation?

Understanding & Learning

Next, I need to understand, why does this code achieve the performance of Class in TS similar to that in Java?

export interface Type<T = any> extends Function {
  new(... args:any[]): T;
}
Copy the code

First of all, it’s defined as interface, so the first point you need to look at is interface;

Function type interface

In the official document, there is this description:

To use the interface to represent function types, we need to define a call signature for the interface. It is like a function definition with only the argument list and return value types. Each parameter in the parameter list needs a name and type.

interface SearchFunc {
  (source: string.subString: string) :boolean;
}
Copy the code

We can be sure that the above method defines a function type interface, which supports passing in a function type; Let’s leave the rest of the puzzle out for a moment and understand the core of the interface, abridged, as follows:

interfaceType{ (... args:any[]) :any;
}
Copy the code

A function type interface in the true sense, the use of this interface means that: the passed argument needs to be a function, and its parameters need to be type checked; In this example, the parameter is… Any [], i.e., no type or quantity constraints; It shows that it does not require method parameters; On this basis, we need to piece together its full meaning; So the next key word is new;

Two, key words: new

In the source code, the method is actually described with one more new keyword than the simplest example of a function-type interface.

export interface{
  new(... args:any[]) :any;
}
Copy the code

[root@stackoverflow] [root@stackoverflow] [root@stackoverflow]

new() describes a constructor signature in typescript. What that means is that it describes the shape of the constructor. For instance take {new(): T; }. You are right it is a type. It is the type of a class whose constructor takes in no arguments.

Translate and delete a ha:

New () is used to describe a constructor;

That is, the interface not only specifies a function type interface, but also specifies the function type interface, which needs to be a constructor function;

OK, now that we know about new, let’s move on;

This code also uses the extends keyword, which brings us to the second point: the inheritance interface;

Inheritance interface

First of all, let’s make it clear what Function is in TS. The answer is: interfaces;

interface Function {
    /** * Returns the name of the function. Function names are read-only and can not be changed. */
    readonly name: string;
}
Copy the code

Type is an interface that inherits Function, which is a Function, and as we all know, TS class is a method, and when compiled, it is a constructor.

  class A {
    constructor(){}}const b = A instanceof Function; // true
Copy the code

Ok, let’s go a little bit further with this understanding;

interface Type extendsFunction{ (... args:any[]) :any;
}
Copy the code

This represents a constructor function interface;

Fourth, generics

export interface Type<T = any> extends Function {
  new(... args:any[]): T;
}
Copy the code

The last piece of knowledge is what the T is. As written above, if any is returned every time, the experience for TS is obviously bad; So how do you describe the type of return? Obviously, you have to use generics; In the official documentation, generics are described as follows:

In software engineering, it is important not only to create consistent, well-defined apis, but also to consider reusability. The ability of components to support not only current data types but also future data types gives you a lot of flexibility when building large systems.

In languages like C# and Java, generics can be used to create reusable components that can support multiple types of data. This allows users to use components with their own data types.

In this example, the generic interface is used:

We might want to treat a generic parameter as a parameter to the entire interface. This way we know exactly which generic type to use (e.g., Dictionary

not just Dictionary). This allows other members of the interface to know the type of the parameter.

interface GenericIdentityFn<T> {
    (arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;
Copy the code

section

A small section of code, in fact, contains the following knowledge points:

  • Function type interface
  • New instantiate method
  • Inherited interface
  • The generic

After understanding the above knowledge points one by one, the writing method will have a clearer understanding;