In TypeScript we use generics to constrain related types of functions. The function here also contains the class constructor, so the declared part of a class can also use generics. So what exactly is generics? What if generics are understood more generally?

What are generics

Generics is the property of defining functions, interfaces, or classes without specifying a specific type in advance, but specifying the type at the time of use.

In popular terms, generics are “parameters” in the type system that are used to reuse types. As you can see from the above definition, it is only used in functions, interfaces, and classes. It’s on a different level (though in the same sense) from function arguments in JS programs, because typescript is a static type system that does type checking at compile time, so generics are actually used at runtime during compilation. It is called a parameter because it has exactly the same properties as a function parameter.

function increse(param) {
  // ...
}Copy the code

In the type system, we use generics like this:

function increase<T>(param: T): T {
  //...
}Copy the code

When param is a type, T is assigned to that type. In the return value, T is that type for type checking.

Build system

Remember that typescript’s own type system requires programming, but in a strange way, you intersperse JS code with its program code. (Interspersing JS code with TS code is a weird thing to say, because we intuitively intersperse TS code with JS code.)

One of the most important forms of programming is the function. Do you see functions in typescript type programming? No. This is because, where there are generics, there are functions, but the form of the function is fragmented by the JS code. Typescript needs to be compiled to get the final product. There are two things to do during compilation. One is to run type programming code in memory to form a type-checking system. That is, we can type js code. Run the system to make type assertions on the JS code interspersed within it; Second, output JS. In the output process, the compilation system has run the code of type programming, just like the PHP code echo JS code, PHP code has been run, the display is JS code.

Looking at typescript in this way, you can probably better understand why it is a superset of JavaScript and why it compiles js (why can’t TS be compiled into other languages?). .

Generics are generally understood

Now that we understand the logic of the TS compilation system, we can emotionally separate type programming from the business programming of JS itself. When we talk about generics, it only exists in the type programming part, which is the compiled runtime code for TS.

Let’s look at a simple example:

function increase<T>(param: T): T {
  //...
}Copy the code

This code, what if we separated out the JS code and represented it with type description text?

// declare the function @type, the argument is T, and the return result is (T): T @type= T => (T): T // Run the function to get a type F, i.e. (number): number @f = @type(number) // Increase is of type F, that is, the argument is number and the return value is number @@ffunction increase(param) { 
  // ... 
} 
@@endCopy the code

There’s actually no @@f syntax, but I made it up so that you can look at the type system in a different way.

Once we understand that generics are “parameters,” we might ask: Where is the function of the type system? With JS functions, you can easily point out function declarations and arguments, but with TS, this part is hidden. However, we can easily see the shadow of a type function in some specific structures:

// Declare a generic interface. This is similar to declaring a function. We use description language to describe @type= T => (T): T interface GenericIdentityFn<T> { (arg: T): T; }} @@[T => (T): T](any)function identity<T>(arg: T): T {
    returnarg; } // Using a generic interface, much like calling a function, we use a description language to describe @type(number)
let myIdentity: GenericIdentityFn<number> = identity;Copy the code

Let’s rewrite the entire code above with the description text:

@GenericIdentityFn = T => (T): T

@@[T => (T): T](any)
function identify(arg) {
  return arg
}
@@end

@@GenericIdentityFn(number)
let myIdentity = identity
@@endCopy the code

We declare two functions in the type system, @GenericIdentityFn and @some (the anonymous function @[T => (T): T]). Although they are two functions, they are actually identical because typescript is a structure type, meaning that type checking only determines whether each node in the structure is of the same type, rather than having to keep the Pointers of the type variables themselves the same. The @genericIdentityfn and @some functions are called to modify identify and myIdentify, respectively, and receive different arguments, resulting in different type checking rules. Identify If the parameter is of the same type as the return value. MyIdentify ensures that the return value of the parameter is of the same type and that the type must be number.

A generic class

In addition to generic interfaces, class classes can also be genericized, that is, “generic classes.” With generic classes, we will explore the declaration and use of generics.

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();Copy the code

Generic interfaces are written like functions because they are only used to constrain the type of functions. In fact, we can redescribe generic interfaces and generic classes in a description language. The red part above, we use descriptive language to describe:

@GenericNumber = T => class {
  zeroValue: T;
  add: (x: T, y: T) => T;
}Copy the code

The @genericNumber function, which takes T as an argument and returns a class, uses T multiple times in the @type function.

@GenericIdentityFn = T => interface { 
  (arg: T): T; 
}Copy the code

We redescribe the previous interface GenericIdentityFn so that we can add additional methods to the interface.

Notice that even after typescript’s built-in base types, such as Array, are declared as generic interfaces and classes, these interfaces and classes must be used with <> arguments, essentially because they are functions that return different values.

Popular explanations for the use of other generics

Next we’ll describe a more complex type:

class Animal {
    numLegs: number;
}

function createInstance<A extends Animal>(c: new () => A): A {
    return new c();
}Copy the code

Instead of looking at the new() part, let’s look at the extends syntax in Angle brackets. What do we make of this? In fact, the question we face is, at compile time, when is the content in <A extends Animal> Angle brackets run, before or between?

// It's @type = (A extends Animal) => (new() => A): A
@type(T) // still at signtype = A => (new() => A): A
@type(T extends Animal)Copy the code

Because typescript is a static type system and Animal is an immutable class, it is possible to assume that the Angle bracket content has already been run before the class is created.

@type = (A extends Animal) => (new() => A): ACopy the code

If T does not satisfy Animal, then the compiler will report an error, not during the type checking phase, but during the type system creation phase. This is the runtime phase of TS code. This condition is known as the “generic constraint.”

In addition, syntax such as <A,B> is consistent with function arguments.

@type = (A, B) => (A|B): SomeTypeCopy the code

Let’s look at the base type of ts: Array<number>

@Array = any => any[]Copy the code

conclusion

Generics in Typescript are, in effect, arguments to generators of types. The content of this article is all imaginary, only applicable to the understanding of TS thinking development, not for real programming, hereby declare.


This article published on my blog: www.tangshuang.net/7473.html

Welcome to talk to me on my podcast, and I hope you can tip me via the QR code below.