Skip to main content

Advanced usage of TypeScript(二)

· 4 min read

This article introduces the advanced uses of TypeScript. it introduces TypeScript from the perspectives of generics and generics tools.

generics are a very important property in TS. They serve as a bridge from static definition to dynamic invocation, as well as the meta programming of TS's own type definitions. generics are the essence of the TS tools, and the hardest part to learn in TS.

basic use

generics can be used with common type definitions, class definitions, and function definitions as follows:

// define a generic type
type Dog<T> = { name: string, type: T }
// use the generic type
const dog: Dog<number> = { name: 'kk', type: 20 }

// define a class using generics
class Cat<T> {
private type: T;
constructor(type: T) { this.type = type; }
}
// use the generic in class
const cat: Cat<number> = new Cat<number>(20); // const cat = new Cat(20)

// define a function using generics
function swipe<T, U>(value: [T, U]): [U, T] {
return [value[1], value[0]];
}
// use the generic in function
swipe<Cat<number>, Dog<number>>([cat, dog]) // swipe([cat, dog])

If you define a generic type for a type name, be sure to include the generic type when using the type name.

For variables whose type can be inferred at call time, generic writing can be omitted.

generic derivation with default values

As mentioned above, we can simplify writing generic type definitions because TS automatically deduces the type of the variable from the type at which it was defined. This is typically the case in function calls.

type Dog<T> = { name: string, type: T };

function adopt<T>(dog: Dog<T>) {
return dog;
}

const dog = { name: "jstyro", type: "website" };
adopt(dog); // type is string

generic constraint

Sometimes we can ignore the specific types of generics, such as:

function fill<T>(length: number, value: T): T[] {
return new Array(length).fill(value);
}

The fill function takes a length argument and a value value to fill the array. We don't need to judge the parameters passed in and just populate them, but sometimes we need to qualify the type, we can use the extends keyword.

function sum<T extends number>(value: T[]): number {
let count = 0;
value.forEach(v => count += v);
return count;
}

This allows you to call the sum function as sum([1,2,3]), whereas something like sum(['1', '2']) will not compile pass.

generic constraints can also be used in the case of multiple generic parameters.

function pick<T, U extends keyof T>(){};

The constraint that U must be a subset of the key types of T.

generic conditions

extends keyword, which can also be used as a ternary operator, looks like this:

T extends U ? X: Y

There is no constraint that T must be a subset of U. If it is a subset of U, T is defined as type X, otherwise it is defined as type Y.

notice that the results generated are distributive.

For example, if we replace X with T: T extends U ? T : never.

The returned T, in this case, is the part of the original T that contains U, which can be understood as the intersection of T and U.

generic infer

infer is usually used with generics conditional statements like the one above. infer means you do not have to infer from the list of generics but rather from the general structure. For example:

type Foo<T> = T extends { t: infer Test } ? Test : string;

{t: infer Test} is the type of the property named t. If T has a property named t, the type of Test is the type of the property named t. Otherwise, the type of Test is string.

Here's an example:

type One = Foo<number>; // string, because number does not contain t
type Two = Foo<{ t: boolean }>; // boolean, because {t: boolean} contains t
type Three = Foo<{ a: number, t: () => void }>; // () => void, because {a: number, t: () => void} contains t

infer is used to derive subtypes from satisfied generic types.